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

99 lines
4.5 KiB
Markdown

<!-- markdownlint-disable MD013 MD060 MD051 -->
# Secrets & security
[← Documentation index](README.md)
> 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](getting_started.md#prerequisites).
Before each deploy, authenticate in **the same shell** in which
`ansible-playbook` then runs:
```bash
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:
```yaml
# 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](../inventories/demo-gymburgdorf/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](../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.