OpenBao Secrets adapter¶
adapters.secrets: {adapter: openbao} keeps the Secrets port in an OpenBao KV version 2 mount, in its own private/ and export/ trees. The credentials follow the layout v5 address of their key; the SSM layout's internal/ and external/ are not used. It needs no platform.openbao answer. Source: internal/port/openbao. The other adapters are listed in adapters.
Settings¶
secrets: {source: file, root: /var/run/sluis/secrets}
adapters:
secrets:
adapter: openbao
settings:
address: https://openbao.example # https, no path
caFile: /var/run/access-issuer/openbao-ca/ca.pem # the CA the server is verified against
namespace: staging # the OpenBao namespace
mount: kv # the KV v2 mount (default kv)
root: sluis # see below
auth:
method: jwt # jwt | kubernetes
mount: jwt-staging # the auth mount (default: the method's name)
role: sluis
tokenFile: /var/run/openbao/token # a projected ServiceAccount token, read again at every login
| Setting | Meaning |
|---|---|
address |
https://host[:port], no path. https only, redirects are not followed |
caFile |
PEM authorities that verify the server. TLS verification is always on |
namespace |
OpenBao namespace; empty is the root namespace |
mount |
KV v2 mount, default kv |
root |
Required. Key hierarchy of the installation under the mount |
auth.method, .mount, .role |
Login POST auth/<mount>/login {role, jwt}. The token is kept per namespace until 80% of its lease has passed |
auth.tokenFile |
The JWT. Required for jwt; kubernetes defaults to the pod's token |
secrets.source stays file: the source delivers inputs, the adapter holds what sluis writes. secrets.root is unused by this adapter.
Layout¶
With namespace staging, mount kv and root sluis:
kv/sluis/private/config/<name> what an operator seeds (read only if used)
kv/sluis/private/credentials/<module>/<kind>/<id>/<ref> what sluis writes and reads back
kv/sluis/export/<path> what sluis copies out, for consumers
Use sluis/<instance> as the root when the OpenBao has no namespace per installation. A root with a private or export segment is refused at start.
Values¶
- A secret is one KV key with one field:
value(text) orvalue_b64(bytes that are not UTF-8). - Seed one with
bao kv put kv/sluis/private/config/<name> value=.... - A JSON object under
export/is stored as one field per property. PutIfVersionis KV check-and-set. The mount must not setcas_required.- Errors name the operation, path and status. Values are never logged.
Policy¶
Least privilege for root sluis and mount kv, in the namespace.
# what sluis writes and reads back
path "kv/data/sluis/private/credentials/*" { capabilities = ["create", "read", "update"] }
path "kv/metadata/sluis/private/credentials/*" { capabilities = ["list", "delete"] }
# the retired exports: nothing writes them
path "kv/data/sluis/export/*" { capabilities = ["create", "read", "update"] }
path "kv/metadata/sluis/export/*" { capabilities = ["list", "delete"] }
# List("") (every secret) lists the two directories themselves; List of a
# prefix under credentials/ or export/ needs only the lines above
path "kv/metadata/sluis/private" { capabilities = ["list"] }
path "kv/metadata/sluis/private/" { capabilities = ["list"] }
path "kv/metadata/sluis/export" { capabilities = ["list"] }
path "kv/metadata/sluis/export/" { capabilities = ["list"] }
# only if configuration secrets are read from OpenBao
path "kv/data/sluis/private/config/*" { capabilities = ["read"] }
Example¶
The k8s-aws preset with KMS-wrapped signing and OpenBao secrets. The pod's AWS role comes from EKS Pod Identity or, with serviceAccount.awsIdentity: irsa, from awsRoleArn.
config:
issuerURL: https://access.example
preset: k8s-aws
signingKey:
file: null # no key file: KMS holds the keys
kmsWrapped:
keyId: alias/sluis-example-sign
region: eu-west-1
stateSecret: issuer/state-secret # a name in `secrets` below
rotateEvery: 24h # the rotation alert fires at 26h
ports:
dynamodb:
region: eu-west-1
tables: {oidc: sluis-oidc, github: sluis-github, slack: sluis-slack,
google: sluis-google, cloudflare: sluis-cloudflare, backup: sluis-backup}
blob: {adapter: s3, s3: {bucket: sluis-blobs, region: eu-west-1}}
adapters:
secrets:
adapter: openbao
settings:
address: https://openbao.example
caFile: /var/run/access-issuer/openbao-ca/ca.pem # adapters.openbao.caBundle
namespace: staging
mount: kv
root: sluis
auth:
method: jwt
mount: jwt-staging
role: sluis
tokenFile: /var/run/openbao/token # adapters.openbao.token.audience
audit: {writer: https://audit.example:8443}
secrets:
- {name: issuer/state-secret, secretName: sluis-inputs, key: state-secret}
serviceAccount:
awsIdentity: pod-identity # or irsa, with awsRoleArn
adapters:
openbao:
caBundle: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
token: {audience: openbao-staging} # a ServiceAccount token projected for the jwt login