reference-ansible/docs/secrets.md
Simon Bärlocher 2ba0c07cd3
docs(reference-ansible): add docs/ tree and document repo, playbooks, Makefile
Addresses the WKS PoC review (Notion 2026-05-26). All docs in English.
- README: purpose, docs table of contents, annotated repo tree
- docs/getting_started.md: prerequisites (WKS account, OIDC, SSH, VPN) + first deploy
- docs/ansible.md: playbook table, "Running Ansible", service parameters, cheatsheet
- docs/secrets.md: canonical Bao login (moved out of README) + demo defaults
- docs/operations.md: full Makefile reference
- docs/inventories.md: repo layout, topology, standard folder structure, walkthrough
- docs/testing.md: static checks, inventory resolution, smoke test / dry run
- remove ARCHITECTURE.md (architecture docs live externally)

Also includes the gymburgdorf inventory build-out (bookstack, homarr,
opnform, send) and scripts/bao-seed.sh. site.yml keeps a third traefik
play (traefik_servers minus the vagrant _dmz/_backend split) so the demo
inventories still configure their reverse proxy after the rebase onto main.
2026-05-28 11:20:54 +02:00

4.5 KiB

Secrets & security

← Documentation index

This repo is explicitly intended for demo setups. All default values in the roles are insecure and are overridden in demo-* inventories via Bao lookups or host_vars.

OpenBao login

A prerequisite is a WKS account with OIDC access to OpenBao and a read policy on the inventory mount — see getting_started.md § Vorbedingungen.

Before each deploy, authenticate in the same shell in which ansible-playbook then runs:

export BAO_ADDR=https://bao.digitalboard.ch
bao login -method=oidc -path=Digitalboard
export VAULT_TOKEN=$(bao print token)

⚠️ make bao alone is not enough — every make target runs in a new shell, and the VAULT_TOKEN set there lives only during make bao itself. Either run the three commands above manually or chain make bao deploy_site_demo_gymburgdorf as one call — otherwise the deploy has no token.

Secret pattern (Bao lookup)

Secrets are never stored in plaintext, but read from OpenBao at runtime:

# host_vars/.../<service>.yml — one lookup per service path,
# individual keys as properties:
_nextcloud: "{{ lookup('community.hashi_vault.hashi_vault',
    vault_mount + '/data/nextcloud', url=vault_addr) }}"
nextcloud_admin_password: "{{ _nextcloud.admin_password }}"
nextcloud_postgres_password: "{{ _nextcloud.postgres_password }}"
  • vault_mount and vault_addr come from group_vars/all/vault.yml.
  • KV-v2 paths need an explicit /data/ in the path — Ansible does not resolve this on its own.
  • vault_mount is unique per inventory (demo-gymburgdorf, demo-phbern, …) → tenant isolation in Bao via mount + policy.

Secrets are seeded idempotently with scripts/bao-seed.sh (or make seed_bao_<kunde>): existing keys remain, only missing ones are generated. OIDC client secrets are kept in sync between <mount>/data/authentik and the respective service secret.

Demo-only defaults — must be overridden

These defaults in digitalboard.core are insecure. In every production-grade deployment they must be overridden via a Bao lookup or host_var:

Variable Default Where to override
keycloak_admin_password changeme host_vars keycloak_servers
keycloak_postgres_password changeme same as above
authentik_secret_key changeme-generate-a-random-string host_vars/application/authentik.yml
authentik_postgres_password changeme same as above
nextcloud_admin_password admin host_vars/application/nextcloud.yml
nextcloud_postgres_password changeme same as above
nextcloud_s3_key / nextcloud_s3_secret changeme / changeme same as above
garage_webui_password admin host_vars/storage/garage.yml
garage_rpc_secret 0123…cdef (64-hex constant) same as above
garage_admin_token identical to rpc_secret same as above
garage_metrics_token identical to rpc_secret same as above

Convention: Every value above must have a Bao lookup in demo-*/host_vars/.../...yml before the inventory counts as deployable.

Threat boundaries (status: demo)

Boundary Status Note
DMZ ↔ backend (172.16.9 ↔ 172.16.19) plaintext HTTP auth bearer, OIDC code, session cookies travel unencrypted. Demo-ok; prod: mTLS or WireGuard overlay.
Host firewall missing The base role installs no UFW/nftables. Segmentation depends on the hypervisor/VLAN.
SSH ansible_user: root No bastion, no jump host. Key distribution out-of-band.
Authentik SPOF accepted IdP and SP services on the same host (application). Authentik outage = login outage including LDAP outpost. No break-glass path.
ACME TSIG key Bao lookup One TSIG key per demo zone (acme_update_key_demo_gymb), zone-isolated. Rotation manual.
Backup/DR out-of-scope Garage replication_factor: 1, no Postgres backup job, no Bao snapshot cron.

Add for production adaptation

  • Host FW (extend the base role or a dedicated firewall role).
  • mTLS or WireGuard between DMZ and backend.
  • Authentik on a separate host, with a recovery admin token.
  • Bao policies per inventory mount (read-only for the deploy token, write-only for the bootstrap job).
  • Backup cron for Postgres + Garage + Bao.
  • SSH bastion + key rotation.