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.
99 lines
4.5 KiB
Markdown
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.
|