Microservice to bring 2FA to self hosted PDSes
1# PDS gatekeeper 2 3A microservice that sits on the same server as the PDS to add some of the security that the entryway does. 4 5![Picture in black and white of a grassy hill with a gate at the top](./images/gate.jpg) 6 7PDS gatekeeper works by overriding some of the PDS endpoints inside your Caddyfile to provide gatekeeping to certain 8endpoints. Mainly, the ability to have 2FA on a self hosted PDS like it does on a Bluesky mushroom(PDS). Most of the 9logic of these endpoints still happens on the PDS via a proxied request, just some are gatekept. 10 11# Features 12 13## 2FA 14 15- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins 16- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA 17 18## Captcha on account creation 19 20Require a `verificationCode` set on the `createAccount` request. This is gotten from completing a captcha challenge 21hosted on the 22PDS mimicking what the Bluesky Entryway does. Migration tools will need to support this, but social-apps will support 23and redirect to `GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT`. This is how the clients know to get the code to prove a captcha 24was successful. 25 26- Requires `GATEKEEPER_CREATE_ACCOUNT_CAPTCHA` to be set to true. 27- Requires `PDS_HCAPTCHA_SITE_KEY` and `PDS_HCAPTCHA_SECRET_KEY` to be set. Can sign up at https://www.hcaptcha.com/ 28- Requires proxying `/xrpc/com.atproto.server.describeServer`, `/xrpc/com.atproto.server.createAccount` and `/gate/*` to 29 PDS 30 Gatekeeper 31- Optional `GATEKEEPER_JWE_KEY` key to encrypt the captcha verification code. Defaults to a random 32 byte key. Not 32 strictly needed unless you're scaling 33- Optional`GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT` default redirect on captcha success. Defaults to `https://bsky.app`. 34- Optional `GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS` allowed redirect urls for captcha success. You want these to match the 35 url showing the captcha. Defaults are: 36 - https://bsky.app 37 - https://pdsmoover.com 38 - https://blacksky.community 39 - https://tektite.cc 40 41## Block account creation unless it's a migration 42 43You can set `GATEKEEPER_ALLOW_ONLY_MIGRATIONS` to block createAccount unless it's via a migration. This does not require 44a change for migration tools, but social-apps create a new account will no longer work and to create a brand new account 45users will need to do this via the Oauth account create screen on the PDS. We recommend setting `PDS_HCAPTCHA_SITE_KEY` 46and `PDS_HCAPTCHA_SECRET_KEY` so the OAuth screen is protected by a captcha if you use this with invite codes turned 47off. 48 49# Setup 50 51PDS Gatekeeper has 2 parts to its setup, docker compose file and a reverse proxy (Caddy in this case). I will be 52assuming you setup the PDS following the directions 53found [here](https://atproto.com/guides/self-hosting), but if yours is different, or you have questions, feel free to 54let 55me know, and we can figure it out. 56 57## Docker compose 58 59The pds gatekeeper container can be found on docker hub under the name `fatfingers23/pds_gatekeeper`. The container does 60need 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 61like below. You can find a full example of what I use for my pds at [./examples/compose.yml](./examples/compose.yml). 62This is usually found at `/pds/compose.yaml`on your PDS> 63 64```yml 65 gatekeeper: 66 container_name: gatekeeper 67 image: fatfingers23/pds_gatekeeper:latest 68 network_mode: host 69 restart: unless-stopped 70 #This gives the container to the access to the PDS folder. Source is the location on your server of that directory 71 volumes: 72 - type: bind 73 source: /pds 74 target: /pds 75 depends_on: 76 - pds 77``` 78 79For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up 80correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml). 81 82```yml 83gatekeeper: 84 container_name: gatekeeper 85 image: 'fatfingers23/pds_gatekeeper:latest' 86 restart: unless-stopped 87 volumes: 88 - '/pds:/pds' 89 environment: 90 - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 91 - 'PDS_BASE_URL=http://pds:3000' 92 - GATEKEEPER_HOST=0.0.0.0 93 depends_on: 94 - pds 95 healthcheck: 96 test: 97 - CMD 98 - timeout 99 - '1' 100 - bash 101 - '-c' 102 - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080' 103 interval: 10s 104 timeout: 5s 105 retries: 3 106 start_period: 10s 107 labels: 108 - traefik.enable=true 109 - '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`))' 110 - traefik.http.routers.pds-gatekeeper.entrypoints=https 111 - traefik.http.routers.pds-gatekeeper.tls=true 112 - traefik.http.routers.pds-gatekeeper.priority=100 113 - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors 114 - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080 115 - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http 116 - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH' 117 - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*' 118 - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*' 119 - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100 120 - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true 121 - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true 122``` 123 124## Caddy setup 125 126For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add 127in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile). 128This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS. 129 130``` 131 @gatekeeper { 132 path /xrpc/com.atproto.server.getSession 133 path /xrpc/com.atproto.server.describeServer 134 path /xrpc/com.atproto.server.updateEmail 135 path /xrpc/com.atproto.server.createSession 136 path /xrpc/com.atproto.server.createAccount 137 path /@atproto/oauth-provider/~api/sign-in 138 path /gate/* 139 } 140 141 handle @gatekeeper { 142 reverse_proxy http://localhost:8080 143 } 144 145 reverse_proxy http://localhost:3000 146``` 147 148If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to 149`localhost:8081` (or w/e port you want). 150 151``` 152http://*.localhost:8082, http://localhost:8082 { 153 @gatekeeper { 154 path /xrpc/com.atproto.server.getSession 155 path /xrpc/com.atproto.server.describeServer 156 path /xrpc/com.atproto.server.updateEmail 157 path /xrpc/com.atproto.server.createSession 158 path /xrpc/com.atproto.server.createAccount 159 path /@atproto/oauth-provider/~api/sign-in 160 path /gate/* 161 } 162 163 handle @gatekeeper { 164 #This is the address for PDS gatekeeper, default is 8080 165 reverse_proxy http://localhost:8080 166 #Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper 167 header_up X-Forwarded-For {http.request.header.CF-Connecting-IP} 168 } 169 reverse_proxy http://localhost:3000 170} 171 172``` 173 174# Environment variables and bonuses 175 176Every environment variable can be set in the `pds.env` and shared between PDS and gatekeeper and the PDS, with the 177exception of `PDS_ENV_LOCATION`. This can be set to load the pds.env, by default it checks `/pds/pds.env` and is 178recommended to mount the `/pds` folder on the server to `/pds` in the pds gatekeeper container. 179 180`PDS_DATA_DIRECTORY` - Root directory of the PDS. Same as the one found in `pds.env` this is how pds gatekeeper knows 181knows the rest of the environment variables. 182 183`GATEKEEPER_EMAIL_TEMPLATES_DIRECTORY` - The folder for templates of the emails PDS gatekeeper sends. You can find them 184in [./email_templates](./email_templates). You are free to edit them as you please and set this variable to a location 185in the pds gateekeper container and it will use them in place of the default ones. Just make sure ot keep the names the 186same. 187 188`GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT` - Subject of the email sent to the user when they turn on 2FA. Defaults to 189`Sign in to Bluesky` 190 191`PDS_BASE_URL` - Base url of the PDS. You most likely want `https://localhost:3000` which is also the default 192 193`GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1` 194 195`GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080` 196 197`GATEKEEPER_CREATE_ACCOUNT_PER_SECOND` - Sets how often it takes a count off the limiter. example if you hit the rate 198limit 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. 199 200`GATEKEEPER_CREATE_ACCOUNT_BURST` - Sets how many requests can be made in a burst. In the prior example this is where 201the 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 202off. 203 204`GATEKEEPER_ALLOW_ONLY_MIGRATIONS` - Defaults false. If set to true, will only allow the 205`/xrpc/com.atproto.server.createAccount` endpoint to be used for migrations. Meaning it will check for the serviceAuth 206token and verify it is valid. 207