PDS gatekeeper#
A microservice that sits on the same server as the PDS to add some of the security that the entryway does.
PDS gatekeeper works by overriding some of the PDS endpoints inside your Caddyfile to provide gatekeeping to certain endpoints. Mainly, the ability to have 2FA on a self hosted PDS like it does on a Bluesky mushroom(PDS). Most of the logic of these endpoints still happens on the PDS via a proxied request, just some are gatekept.
Features#
2FA#
- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins
- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA
Captcha on Create Account#
Future feature?
Setup#
PDS Gatekeeper has 2 parts to its setup, docker compose file and a reverse proxy (Caddy in this case). I will be assuming you setup the PDS following the directions found here, but if yours is different, or you have questions, feel free to let me know, and we can figure it out.
Docker compose#
The pds gatekeeper container can be found on docker hub under the name fatfingers23/pds_gatekeeper. The container does
need access to the /pds root folder to access the same db's as your PDS. The part you need to add would look a bit
like below. You can find a full example of what I use for my pds at ./examples/compose.yml.
This is usually found at /pds/compose.yamlon your PDS>
gatekeeper:
container_name: gatekeeper
image: fatfingers23/pds_gatekeeper:latest
network_mode: host
restart: unless-stopped
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
volumes:
- type: bind
source: /pds
target: /pds
depends_on:
- pds
For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up correctly. A full example can be found at ./examples/coolify-compose.yml.
gatekeeper:
container_name: gatekeeper
image: 'fatfingers23/pds_gatekeeper:latest'
restart: unless-stopped
volumes:
- '/pds:/pds'
environment:
- 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}'
- 'PDS_BASE_URL=http://pds:3000'
- GATEKEEPER_HOST=0.0.0.0
depends_on:
- pds
healthcheck:
test:
- CMD
- timeout
- '1'
- bash
- '-c'
- 'cat < /dev/null > /dev/tcp/0.0.0.0/8080'
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
labels:
- traefik.enable=true
- 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))'
- traefik.http.routers.pds-gatekeeper.entrypoints=https
- traefik.http.routers.pds-gatekeeper.tls=true
- traefik.http.routers.pds-gatekeeper.priority=100
- traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors
- traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080
- traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH'
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*'
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*'
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100
- traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
Caddy setup#
For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add
in extra functionality. The main part is below, for a full example see ./examples/Caddyfile.
This is usually found at /pds/caddy/etc/caddy/Caddyfile on your PDS.
@gatekeeper {
path /xrpc/com.atproto.server.getSession
path /xrpc/com.atproto.server.updateEmail
path /xrpc/com.atproto.server.createSession
path /xrpc/com.atproto.server.createAccount
path /@atproto/oauth-provider/~api/sign-in
}
handle @gatekeeper {
reverse_proxy http://localhost:8080
}
reverse_proxy http://localhost:3000
If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to
localhost:8081 (or w/e port you want).
http://*.localhost:8082, http://localhost:8082 {
@gatekeeper {
path /xrpc/com.atproto.server.getSession
path /xrpc/com.atproto.server.updateEmail
path /xrpc/com.atproto.server.createSession
path /xrpc/com.atproto.server.createAccount
path /@atproto/oauth-provider/~api/sign-in
}
handle @gatekeeper {
reverse_proxy http://localhost:8080 {
#Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
}
}
reverse_proxy http://localhost:3000
}
Environment variables and bonuses#
Every environment variable can be set in the pds.env and shared between PDS and gatekeeper and the PDS, with the
exception of PDS_ENV_LOCATION. This can be set to load the pds.env, by default it checks /pds/pds.env and is
recommended to mount the /pds folder on the server to /pds in the pds gatekeeper container.
PDS_DATA_DIRECTORY - Root directory of the PDS. Same as the one found in pds.env this is how pds gatekeeper knows
knows the rest of the environment variables.
GATEKEEPER_EMAIL_TEMPLATES_DIRECTORY - The folder for templates of the emails PDS gatekeeper sends. You can find them
in ./email_templates. You are free to edit them as you please and set this variable to a location
in the pds gateekeper container and it will use them in place of the default ones. Just make sure ot keep the names the
same.
GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT - Subject of the email sent to the user when they turn on 2FA. Defaults to
Sign in to Bluesky
PDS_BASE_URL - Base url of the PDS. You most likely want https://localhost:3000 which is also the default
GATEKEEPER_HOST - Host for pds gatekeeper. Defaults to 127.0.0.1
GATEKEEPER_PORT - Port for pds gatekeeper. Defaults to 8080
GATEKEEPER_CREATE_ACCOUNT_PER_SECOND - Sets how often it takes a count off the limiter. example if you hit the rate
limit of 5 and set to 60, then in 60 seconds you will be able to make one more. Or in 5 minutes be able to make 5 more.
GATEKEEPER_CREATE_ACCOUNT_BURST - Sets how many requests can be made in a burst. In the prior example this is where
the 5 comes from. Example can set this to 10 to allow for 10 requests in a burst, and after 60 seconds it will drop one
off.