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.
4.5 KiB
Secrets & security
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 baoalone is not enough — everymaketarget runs in a new shell, and theVAULT_TOKENset there lives only duringmake baoitself. Either run the three commands above manually or chainmake bao deploy_site_demo_gymburgdorfas 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_mountandvault_addrcome 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_mountis 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/.../...ymlbefore 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
baserole or a dedicatedfirewallrole). - 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.