diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..422e345 --- /dev/null +++ b/.env.example @@ -0,0 +1,48 @@ +# ~$ selfhostedops — copy me: cp .env.example .env +# Generate secrets with: openssl rand -hex 32 + +# Your base domain. Services live at git.* wiki.* vault.* status.* n8n.* vpn.* +# DNS: one wildcard A (and AAAA) record *.ops.example.com -> your server IP +DOMAIN=ops.example.com + +# Let's Encrypt notifications + expiry mails +ACME_EMAIL=you@example.com + +TZ=Europe/Berlin + +# --- Outline --- +OUTLINE_PG_PASSWORD=CHANGE_ME_openssl_rand_hex_32 +OUTLINE_SECRET_KEY=CHANGE_ME_openssl_rand_hex_32 +OUTLINE_UTILS_SECRET=CHANGE_ME_openssl_rand_hex_32 +# From Forgejo: Site Administration -> Applications -> OAuth2 +# Redirect URI: https://wiki./auth/oidc.callback +OUTLINE_OIDC_CLIENT_ID= +OUTLINE_OIDC_CLIENT_SECRET= + +# --- Vaultwarden --- +# Admin panel token (argon2 hash recommended, plain string works to start) +VAULTWARDEN_ADMIN_TOKEN=CHANGE_ME_openssl_rand_hex_32 +# Flip to false once your team has signed up: +VAULTWARDEN_SIGNUPS_ALLOWED=true +# Single sign-on against your own Forgejo (episode 4). +# From Forgejo: Site Administration -> Applications -> OAuth2 Application +# Redirect URI must be EXACTLY: https://vault./identity/connect/oidc-signin +VAULTWARDEN_SSO_CLIENT_ID= +VAULTWARDEN_SSO_CLIENT_SECRET= + +# --- wg-easy (optional, --profile vpn) --- +# Generate with: docker run --rm ghcr.io/wg-easy/wg-easy wgpw 'yourpassword' +WGEASY_PASSWORD_HASH= + +# --- CI (episode 2, optional: docker compose --profile ci up -d) --- +# One shared secret registers the runner declaratively. Generate it with: +# openssl rand -hex 20 +# Then derive the UUID Forgejo will know it by: +# python3 -c 'import uuid,sys;print(uuid.UUID(bytes=sys.argv[1][:16].encode()))' +# And register it on the Forgejo side: +# docker compose exec -u git forgejo forgejo forgejo-cli actions register \ +# --name box-runner --labels docker --secret +# A registration token copied from the web UI does NOT work here. +RUNNER_SECRET= +RUNNER_UUID= + diff --git a/HARDENING.md b/HARDENING.md index f2b3358..c773553 100644 --- a/HARDENING.md +++ b/HARDENING.md @@ -58,6 +58,20 @@ apt install -y fail2ban # default ssh jail is enough to start - `VAULTWARDEN_SIGNUPS_ALLOWED=false` the moment your team is on board. - Use an argon2 `ADMIN_TOKEN` (`vaultwarden hash` / see wiki), not a plain string. + A plain one makes Vaultwarden print a NOTICE on every start. It is telling + the truth. +- **Never back up `db.sqlite3` on its own while the container runs.** The + database is in WAL mode: recent writes live in `db.sqlite3-wal` and are not + in the main file yet. Copy it alone and you get a backup that silently + misses your newest users and entries, and you find out on restore day. + Either stop the container first (see section 7), or take a consistent copy: + +```bash +docker compose exec vaultwarden sh -c "sqlite3 /data/db.sqlite3 \".backup '/data/backup.sqlite3'\"" +``` + +- Restore the backup somewhere else, once, before you trust it. A backup you + have never restored is a belief, not a backup. - The admin panel (`/admin`) is for setup — consider blocking it in Caddy afterwards: ``` @@ -89,7 +103,7 @@ Plus: enable your provider's server snapshots. Two clicks, saves your company. ## 8. What this list is NOT -Not covered here, on purpose (own episodes): SSO everywhere, 2FA enforcement +Not covered here, on purpose (own episodes): 2FA enforcement per service, Netbird zero-trust mesh, central logging, CrowdSec, offsite borg. Baseline first. Perfect later. diff --git a/README.md b/README.md index be145e7..5e38b33 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,55 @@ command (`examples/deploy-hello-ops.sh`). Read the CI section of [HARDENING.md](HARDENING.md) before you wire the deploy step — there are two comfortable ways to hand your whole server to anyone who can push a branch. +## One login for the vault (Episode 4) + +Your Forgejo already knows your team. Let it log them into the password +manager too, so offboarding is one switch instead of a checklist. + +```bash +# 1. Create the OAuth2 app in Forgejo +# Site Administration -> Applications -> OAuth2 Application +# Name: Vaultwarden +# Redirect URI: https://vault./identity/connect/oidc-signin +# Confidential client: yes +# +# That redirect path is not documented anywhere in the UI, and a wrong one +# only fails AFTER the Forgejo login. Copy it character for character. + +# 2. Put the client id and secret in .env +VAULTWARDEN_SSO_CLIENT_ID=... +VAULTWARDEN_SSO_CLIENT_SECRET=... + +# 3. Restart just that container +docker compose up -d vaultwarden + +# 4. Log in: https://vault. +# Type your work mail, hit "Use single sign-on", authorize once in Forgejo. +``` + +Vaultwarden will then ask you to set a master password. That is not a bug. +Forgejo proved *who you are*; nothing so far produced the key that *decrypts +your vault*. Your master password never leaves your browser, and the server +stores blobs it cannot read. Two different jobs, two different mechanisms. + +Debugging notes, all of them learned the hard way: + +- Vaultwarden logs nothing about SSO at startup. It builds the OIDC client on + the first login attempt, not before. Watch `docker compose logs -f + vaultwarden` *while* you log in instead: you will see a `prevalidate` call + and then the `oidc-signin` callback coming back with a code. +- `/identity/connect/authorize` and the callback both answer `404` when called + without query parameters. That is route matching, not a wrong path. +- `curl -I` on Forgejo's `/.well-known/openid-configuration` returns 404 because + there is no HEAD route. Use `curl -s` and it is a normal 200. +- Turn on `SSO_SIGNUPS_MATCH_EMAIL` **before** anyone logs in, or you get + duplicate accounts instead of linked ones. + +Then make it a team vault: create an organization, add collections, invite +people. Note that joining is three steps, not two: you invite, they log in, +and then an owner has to **confirm** them. Until that confirmation, a member +sees no shared items at all. + ## Hardening Before you put real company data on this box, read **[HARDENING.md](HARDENING.md)**. diff --git a/compose.yml b/compose.yml index 935ce7c..4306c7c 100644 --- a/compose.yml +++ b/compose.yml @@ -126,6 +126,24 @@ services: DOMAIN: https://vault.${DOMAIN} ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN} SIGNUPS_ALLOWED: "${VAULTWARDEN_SIGNUPS_ALLOWED:-true}" # set to false after your team joined! + # --- Single sign-on via your own Forgejo (episode 4) --- + # Create the OAuth2 app in Forgejo FIRST: Site Administration -> + # Applications -> OAuth2. Its redirect URI must be exactly: + # https://vault.${DOMAIN}/identity/connect/oidc-signin + # That path is not documented in the UI. Getting it wrong fails only + # AFTER the Forgejo login, which is why it costs people twenty minutes. + SSO_ENABLED: "true" + # Just the base URL. Vaultwarden reads Forgejo's discovery document + # (/.well-known/openid-configuration) from here on the first login. + SSO_AUTHORITY: https://git.${DOMAIN} + SSO_CLIENT_ID: ${VAULTWARDEN_SSO_CLIENT_ID} + SSO_CLIENT_SECRET: ${VAULTWARDEN_SSO_CLIENT_SECRET} + # Vaultwarden prepends "openid" itself, so listing it here again shows up + # duplicated on the Forgejo consent screen. Harmless, but untidy. + SSO_SCOPES: "profile email" + # Attach an SSO login to the existing account with the same mail address + # instead of creating a duplicate. Turn this on BEFORE people log in. + SSO_SIGNUPS_MATCH_EMAIL: "true" volumes: - vaultwarden_data:/data networks: [proxy]