Episode 15: Pocket ID as identity provider, tinyauth gate, offboarding script
Pocket ID replaces Forgejo as the OIDC provider for Outline, Vaultwarden and NetBird; Forgejo logs in through it. Uptime Kuma sits behind tinyauth via Caddy forward_auth (group ops), the public status page stays public. offboarding/offboard.py switches one person off in every service with an API and prints what a human still has to do: the shared passwords they could read. Measured: SSO lock alone left 10 of 13 ways open for 45 minutes; the script leaves 1 of 14, the vault copy on their laptop. HARDENING.md: the ufw hairpin that broke Vaultwarden SSO and Kuma, the mesh route into the docker bridges, and a fail2ban exception for Pocket ID.
This commit is contained in:
parent
3edef38ba3
commit
12542ecb19
9 changed files with 490 additions and 21 deletions
78
offboarding/README.md
Normal file
78
offboarding/README.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Offboarding (episode 15)
|
||||
|
||||
Someone leaves. Switching them off in the single sign-on is not offboarding.
|
||||
Measured on the demo box, with a test employee who had the access a real one has:
|
||||
|
||||
| After | Ways still open |
|
||||
| --- | --- |
|
||||
| Forgejo login prohibited (Forgejo was the SSO provider) | **10 of 13**, unchanged for 45 minutes |
|
||||
| Pocket ID account disabled, nothing else | **12 of 14** |
|
||||
| `./offboard.py <user>` (about one second) | **1 of 14**: the vault copy on their laptop |
|
||||
|
||||
What stayed open with only the SSO switched off: the wiki session (valid for
|
||||
three months) and its API key, the vault sync (Forgejo kept handing out fresh
|
||||
tokens to the locked account), a new vault login with the master password, the
|
||||
local vault copy, the mesh peer (expires after 24 h), the automation account,
|
||||
both notification logins, and the monitoring password everybody knows.
|
||||
|
||||
## Run it
|
||||
|
||||
On the box, as root:
|
||||
|
||||
```sh
|
||||
cd /opt/selfhostedops-stack/offboarding
|
||||
cp offboard.env.example offboard.env && chmod 600 offboard.env # fill in once
|
||||
./offboard.py sam --dry-run # looks, changes nothing
|
||||
./offboard.py sam
|
||||
```
|
||||
|
||||
It disables the person in Pocket ID first (no new logins, refresh tokens
|
||||
refused), then per service: deletes the tinyauth gate sessions, prohibits the
|
||||
Forgejo login and deletes the SSH keys, suspends the Outline user, disables the
|
||||
Vaultwarden user (which also logs out every device), deletes the NetBird user
|
||||
with its peers, deletes the n8n user, deletes the ntfy user with its tokens.
|
||||
Every step can run twice. A step that fails says why and the others still run.
|
||||
|
||||
Then it prints what only a human can do. The important line is this one:
|
||||
|
||||
```
|
||||
Vaultwarden: remove the user from organization 'Northwind Labs' (needs an org
|
||||
owner) and CHANGE THE 6 SHARED PASSWORDS they could read. A copy on their
|
||||
laptop still opens with their master password, offline, forever.
|
||||
```
|
||||
|
||||
That is not a figure of speech. After the script, on the test laptop, with the
|
||||
network namespace cut off, `bw unlock` still opened all six shared entries in
|
||||
clear text. No server-side switch reaches a file on someone else's disk.
|
||||
Rotation is the offboarding step for everything they could read.
|
||||
|
||||
## What the keys in offboard.env may do
|
||||
|
||||
| Key | Scope | Why |
|
||||
| --- | --- | --- |
|
||||
| `POCKET_ID_API_KEY` | admin API, set an expiry | disable the user |
|
||||
| `FORGEJO_ADMIN_TOKEN` | `write:admin`, `write:user` | prohibit login, delete keys, list tokens |
|
||||
| `OUTLINE_API_KEY` | an admin's key | suspend |
|
||||
| `NETBIRD_PAT` | service user, role admin, 30 days | delete user and peers |
|
||||
| `N8N_API_KEY` | only `user:list`, `user:read`, `user:delete` | delete the user |
|
||||
|
||||
This file is the most powerful thing on the box after root. Keep it at 600,
|
||||
give every key an expiry, and renew them on purpose.
|
||||
|
||||
## Traps we hit while building it
|
||||
|
||||
- **Vaultwarden admin routes want JSON.** `POST /admin/users/<id>/disable`
|
||||
without a JSON content type is not a 403, it is a 404, and it looks exactly
|
||||
like "user not found".
|
||||
- **Vaultwarden rate-limits the admin login** (a handful per five minutes). Two
|
||||
test runs back to back and the third one gets 429. The script says so.
|
||||
- **An old n8n API key cannot delete users.** Keys created before scopes
|
||||
existed, or with the wrong ones, get 403. Create one with `user:delete`.
|
||||
- **Forgejo tokens:** an admin can list a user's tokens (`Sudo:` header) but
|
||||
deleting one needs the owner's password. `prohibit_login` blocks them, so the
|
||||
script names them instead.
|
||||
- **Outline 1.10** lists an admin only their own API keys. Suspending the user
|
||||
refuses their keys and browser sessions anyway (measured: 401 and 403).
|
||||
- **Group restriction in Pocket ID is a separate switch.** Setting allowed
|
||||
groups on a client via the API leaves `isGroupRestricted` false, and the UI
|
||||
then says "Any user can sign in to this client". Set both.
|
||||
Loading…
Add table
Add a link
Reference in a new issue