Skip to content

Commit 1aca727

Browse files
committed
docs(openbao): document the static seal trade-off and the Shamir migration
With the static seal the key sits in a Secret in the same cluster as the data it unwraps, so the barrier is only as strong as access to Secrets in that namespace, to etcd and to backups. The README now says so before the reader picks a seal, and it says what the key identifier is for, since OpenBAO picks the key for stored data by it. Tenant roles grant nothing on Secrets and no pods/log, so a cluster administrator creates the key Secret and reads the log line that confirms a re-wrap. The StatefulSet uses the OnDelete strategy, so neither a seal migration nor the apiserver egress label reaches a running pod until it is replaced. The README describes the migration from Shamir step by step, following the OpenBAO seal migration guide, and says that pods created before the label have to be replaced once. Assisted-by: LLM Signed-off-by: Yan Bondarenko <[email protected]>
1 parent 5f33b21 commit 1aca727

1 file changed

Lines changed: 30 additions & 13 deletions

File tree

‎packages/apps/openbao/README.md‎

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ It provides identity-based secrets and encryption management for cloud infrastru
77
88
## Auto-unseal
99

10-
By default OpenBAO uses Shamir key shares: after every pod restart an operator has to unseal each replica by hand. Setting `seal.type: static` switches the instance to OpenBAO's [`seal "static"`](https://openbao.org/docs/configuration/seal/static/), which unseals the barrier from a 32-byte key read from a Secret you manage. The chart never generates, stores or rotates the key; it only mounts the Secret you name.
10+
By default OpenBAO uses Shamir key shares, so after every pod restart an operator has to unseal each replica by hand. Setting `seal.type: static` switches the instance to OpenBAO's [`seal "static"`](https://openbao.org/docs/configuration/seal/static/), which unseals the barrier from a 32-byte key read from a Secret in the tenant namespace. The chart never generates, stores or rotates the key. It only mounts the Secret you name.
1111

12-
1. Create the key Secret in the release namespace. The value is the base64 text of 32 random bytes, stored under the key `key`:
12+
The choice is a trade-off. With `static` the key sits in a Secret in the same cluster as the data it unwraps, so the barrier is only as strong as access to Secrets in that namespace, to etcd and to backups. Shamir keeps the key material out of the cluster at the price of a manual unseal after every restart. Pick `static` when availability after a restart matters more than that boundary.
13+
14+
1. A cluster administrator creates the key Secret in the tenant namespace. Tenant roles grant no access to Secrets, so tenant users can neither create the key nor read it back. The pods mount the Secret and do not start until it exists, so have it created before you set `seal.type: static`. The value is the base64 text of 32 random bytes, stored under the key `key`.
1315

1416
```bash
1517
kubectl -n tenant-<name> create secret generic openbao-unseal \
@@ -25,32 +27,47 @@ By default OpenBAO uses Shamir key shares: after every pod restart an operator h
2527
keyId: key-1
2628
```
2729
28-
`keyId` is a permanent identifier for that key material. OpenBAO refuses to start with a key whose identifier it has already seen with different material, so change `keyId` whenever you change the key.
30+
`keyId` is a permanent identifier for that key material. OpenBAO stores it with the data the key wraps and picks the key by it, so change `keyId` whenever you change the key.
2931

3032
To rotate the key:
3133

32-
1. Create a second Secret with the new key material and pick a new `keyId`.
33-
2. Move the current pair to `previousSecretName`/`previousKeyId`, put the new pair in `secretName`/`keyId` and upgrade the release. This only changes the StatefulSet template. The upstream chart uses the `OnDelete` update strategy and the server copies its HCL to `/tmp/storageconfig.hcl` at startup, so running pods keep the old key and the old config until they are replaced.
34-
3. Replace the pods. Standalone: delete the single pod. HA: delete the standby pods one at a time, waiting for each to rejoin and unseal, then delete the active pod so that leadership moves to a pod that already runs the new config. Do not delete the active pod first: once a node with the new config has taken over, a standby still on the old template cannot unseal after a restart.
34+
1. Have a cluster administrator create a second Secret with the new key material, and pick a new `keyId`.
35+
2. Move the current pair to `previousSecretName`/`previousKeyId`, put the new pair in `secretName`/`keyId` and upgrade the release. The upgrade changes the config and the StatefulSet template, not the running pods. The upstream chart uses the `OnDelete` update strategy and the server copies its HCL to `/tmp/storageconfig.hcl` at startup, so running pods keep the old key and the old config until they are replaced.
36+
3. Replace the pods. In standalone mode delete the single pod. In HA mode delete the standby pods one at a time, waiting for each to rejoin and unseal, then delete the active pod so that leadership moves to a pod that already runs the new config. Do not delete the active pod first. Once a node with the new config has taken over, a standby still on the old template cannot unseal after a restart.
3537

3638
```bash
37-
kubectl -n <namespace> delete pod <release>-2 # standby
38-
kubectl -n <namespace> delete pod <release>-1 # standby
39-
kubectl -n <namespace> delete pod <release>-0 # active, last
39+
kubectl -n <namespace> get pods -l openbao-active=true # the active pod
40+
kubectl -n <namespace> delete pod <standby pod> # each standby, one at a time
41+
kubectl -n <namespace> delete pod <active pod> # last
4042
```
4143

42-
4. Confirm the rotation before touching the old key. The node that becomes active with the new config re-wraps the stored barrier and recovery keys with `current_key` and logs it:
44+
4. Confirm the rotation before touching the old key. The node that becomes active with the new config re-wraps the stored barrier and recovery keys with `current_key` and logs it. Tenant roles do not grant `pods/log`, so a cluster administrator reads that log.
4345

4446
```bash
4547
kubectl -n <namespace> logs <active pod> | grep -E 'upgrading (stored keys|recovery key)'
4648
```
4749

4850
Until those lines have appeared, storage is still wrapped with the previous key. The static seal decrypts by the key identifier stored with the data and refuses an identifier it does not know (`unknown key id for data`), which is why both keys have to stay configured until the re-wrap is done.
49-
5. Drop the `previous*` values, upgrade, and replace the pods once more in the same order. A pod that unseals with the new key alone proves the re-wrap. Delete the old Secret last.
51+
5. Drop the `previous*` values, upgrade, and replace the pods once more in the same order. A pod that unseals with the new key alone proves the re-wrap. The cluster administrator deletes the old Secret last.
52+
53+
### Upgrading an existing instance
54+
55+
The chart labels the server pods with `policy.cozystack.io/allow-to-apiserver: "true"`, which the tenant network policy needs before `service_registration "kubernetes"` can reach the API server (#2793). The StatefulSet uses the `OnDelete` strategy, so pods created before the label was added keep running without it after an upgrade. Delete them once, in HA mode one at a time, so that they come back labelled. With the Shamir seal every replaced pod has to be unsealed again. An HA instance that hangs at start because of #2793 stays hung until its pods are replaced.
56+
57+
### Migrating an existing instance from Shamir
58+
59+
Switching a running Shamir-sealed instance to `static` is a seal migration, see the OpenBAO [seal migration](https://openbao.org/docs/concepts/seal/#seal-migration) documentation. The chart does not run it for you. It refuses a changed `seal.type` on an instance that already runs with another seal, because a plain config change leaves an instance that cannot unseal. OpenBAO documents the migration as a procedure with downtime, so plan a window.
60+
61+
1. Take a restorable backup first, as OpenBAO recommends before any seal migration. In HA mode `bao operator raft snapshot save <file>` writes it, in standalone mode copy the data volume.
62+
2. A cluster administrator creates the key Secret as described above.
63+
3. Set `seal.type: static` with `secretName` and `keyId`, add `seal.allowMigration: true` and upgrade the release. The StatefulSet uses the `OnDelete` strategy, so the running pods keep the Shamir configuration until they are replaced.
64+
4. Standalone. Delete the pod. When it is back, run `bao operator unseal -migrate` with each Shamir unseal key. The server performs the migration and the Shamir keys become the recovery keys.
65+
5. HA. Delete one standby pod, wait for it to come back and run `bao operator unseal -migrate` on it with each Shamir unseal key. Repeat for the other standby pods one at a time. Then run `bao operator step-down` on the active node and wait until another node has become active. The new active node performs the migration and logs its completion. Give that a little time to replicate to the other Raft nodes, then delete the old active pod, so that it restarts with the static configuration and unseals on its own.
66+
6. Remove `seal.allowMigration` and upgrade the release once more.
5067

51-
> Switching an existing Shamir-sealed instance to `static` is a seal migration and needs `bao operator unseal -migrate`; see the OpenBAO [seal migration](https://openbao.org/docs/concepts/seal/#seal-migration) documentation. The chart does not run the migration for you. It does refuse to change `seal.type` on an instance that already runs with another seal, because a plain config change would leave a barrier nobody can decrypt. Set `seal.allowMigration: true` only for the upgrade in which you run the migration, then remove it. Migrating away from `static` needs the old stanza kept with `disabled = "true"` and its key still mounted, which this chart does not render; that direction is refused regardless of `allowMigration`.
68+
Migrating away from `static` needs the old stanza kept with `disabled = "true"` and its key still mounted, which this chart does not render. That direction is refused regardless of `allowMigration`.
5269

53-
Keep a copy of the key outside the cluster: a Raft snapshot or a data PVC is unreadable without it.
70+
Keep a copy of the key outside the cluster. A Raft snapshot or a data PVC is unreadable without it.
5471

5572
## Parameters
5673

0 commit comments

Comments
 (0)