Measured clean run: Pocket ID disable alone left 12 of 14 ways open, the script (0.9 s, 9 of 9 steps) left 1 of 14.
79 lines
3.8 KiB
Markdown
79 lines
3.8 KiB
Markdown
# 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>` (0.9 s, all 9 steps ok) | **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 now waits 60 s
|
|
and retries, up to six times, and prints each wait.
|
|
- **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.
|