Skip to content

Generic PDS proxy#2425

Merged
dholms merged 9 commits intomainfrom
generic-proxy
May 9, 2024
Merged

Generic PDS proxy#2425
dholms merged 9 commits intomainfrom
generic-proxy

Conversation

@dholms
Copy link
Collaborator

@dholms dholms commented Apr 18, 2024

Allow the PDS to proxy generic /xrpc/ endpoints based on atproto-proxy.

Auth with the PDS is validated and a service auth token for the did is signed and attached to the outgoing request.

): Promise<{ url: URL; headers: { authorization?: string } }> => {
const proxyTo = await parseProxyHeader(ctx, req)
const defaultProxy = defaultService(ctx, req.path)
const defaultProxy = defaultService(ctx, req.baseUrl)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mount path doesn't get included in req.path (https://expressjs.com/en/4x/api.html#req.path) so had to change to baseUrl

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this works due to an implementation detail of xrpc-server, that it happens to write its catchall route this way:

    this.router.use(
      '/xrpc/:methodId', // <- baseUrl comes from here
      opts?.catchall ?? this.catchall.bind(this),
    )

If we're able to base this on req.originalUrl (need to account for it containing query params) I think we may be able to avoid a potential footgun. Alternately can probably carefully concat req.baseUrl and req.path together.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup yup totally 👍

ended up rewriting to use req.originalUrl

also fwiw, this logic is only used to find the default service so it's relatively low stakes 👌

Copy link
Collaborator

@bnewbold bnewbold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple thoughts (I didn't look carefully enough to see if these are implemented):

  • only allow exactly /xrpc/<nsid>: no additional path components, no /xrpc/../blah, etc
  • only authenticated users (looks like this is enforced?)
  • atproto-accept-labelers, accept-language, content-type, probably a few other HTTP headers passed through (some request, some response)
  • bodies: JSON probably the priority, but might want to support other mimetypes? presumably things like size limits are enforced by load-balancers already
  • 502 Bad Gateway and 504 Gateway Timeout when needed? any 5xx or 4xx status codes can probably be passed back as-is?
  • if the upstream redirects (3xx), PDS could follow the redirects a couple hops, or return an error to user, but probably shouldn't return the redirect to the user
  • to start, just the usual rate-limits?
  • probably shouldn't support streams (websocket) in this early impl (!)
  • will probably need to figure out some way to hint the level of auth ("scope") at some point: app-password, OAuth scope, etc

Comment on lines +21 to +22
try {
const auth = await ctx.authVerifier.access({ req })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be kind of nice to bail early and not bother with the work of auth'ing if the request is non-lexrpc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice - i split up the createUrlAndHeaders function and now do url + audience before checking auth and then create the signed headers after checking auth 👌

let res: Response
let buffer: ArrayBuffer
try {
res = await fetch(url, reqInit)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to look out for in general is ensuring we always read the request body. By not reading the body, I think we'd end-up leaking connections and other resources.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're all set on this, but just wanted to put it out there!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup good looks 👍

think we always do read it - but taking a look back at it, i'm gonna adjust the error handling just a smidge 👌

Copy link
Collaborator

@devinivy devinivy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. This is gonna rule.

@dholms dholms merged commit c37b0e4 into main May 9, 2024
@dholms dholms deleted the generic-proxy branch May 9, 2024 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants