68 lines
2.8 KiB
HCL
68 lines
2.8 KiB
HCL
|
|
// Policy for the Centralis control-plane container on vps2.
|
||
|
|
//
|
||
|
|
// Centralis is the tool an operator drives a rotation THROUGH, so it needs more than
|
||
|
|
// a host render does: it must enumerate key names across every environment to build
|
||
|
|
// `secrets_inventory`, and write values to perform `secrets_rotate`.
|
||
|
|
//
|
||
|
|
// The split below is the important part. Centralis gets:
|
||
|
|
// * METADATA read/list on every path -> it can build a names-and-versions
|
||
|
|
// inventory across all three environments WITHOUT being able to read a value.
|
||
|
|
// * DATA write on every path -> it can rotate.
|
||
|
|
// * DATA read on NOTHING.
|
||
|
|
//
|
||
|
|
// That is a genuine improvement on the SOPS design, not a port of it. Today
|
||
|
|
// `secrets_inventory` can list key names without a decryption key only because SOPS
|
||
|
|
// happens to leave key names as plaintext in the YAML — a property of the file
|
||
|
|
// format, relied on deliberately (tools/secrets.ts:199 `extractKeyNames`). Under
|
||
|
|
// OpenBao the same "names, never values" guarantee becomes an ENFORCED capability
|
||
|
|
// boundary instead of a fortunate accident. Centralis cannot print a secret it is
|
||
|
|
// not able to fetch.
|
||
|
|
//
|
||
|
|
// The `read` omission is load-bearing: Centralis holds the Docker socket (root
|
||
|
|
// -equivalent on prod) and an SSH private key to beta and dev. Granting it data read
|
||
|
|
// on prod's secrets would make a Centralis compromise equivalent to full estate
|
||
|
|
// credential disclosure. It already effectively is via the Docker socket — but there
|
||
|
|
// is no reason to hand it a second, easier path.
|
||
|
|
|
||
|
|
// Names and version history across the whole tree — no values.
|
||
|
|
path "thermograph/metadata/*" {
|
||
|
|
capabilities = ["read", "list"]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Rotation: write a new version. Note "create" + "update" but NOT "read":
|
||
|
|
// OpenBao permits a blind write, which is exactly the shape secrets_rotate wants —
|
||
|
|
// it mints or receives a new value and stores it, and never needs the old one.
|
||
|
|
path "thermograph/data/*" {
|
||
|
|
capabilities = ["create", "update"]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Its OWN configuration is the one place Centralis may read, because it must render
|
||
|
|
// /etc/centralis.env for itself. This is the direct analogue of centralis.prod.yaml.
|
||
|
|
path "thermograph/data/centralis/prod" {
|
||
|
|
capabilities = ["read", "create", "update"]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Version rollback, so a bad rotation is recoverable through the control plane
|
||
|
|
// rather than requiring a shell on the box. This is the capability that replaces
|
||
|
|
// "revert the PR" in the SOPS model.
|
||
|
|
path "thermograph/undelete/*" {
|
||
|
|
capabilities = ["update"]
|
||
|
|
}
|
||
|
|
|
||
|
|
path "thermograph/destroy/*" {
|
||
|
|
capabilities = ["deny"]
|
||
|
|
}
|
||
|
|
|
||
|
|
path "auth/token/lookup-self" {
|
||
|
|
capabilities = ["read"]
|
||
|
|
}
|
||
|
|
|
||
|
|
path "auth/token/renew-self" {
|
||
|
|
capabilities = ["update"]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Read its own AppRole role-id for self-diagnosis, but never the secret-id.
|
||
|
|
path "auth/approle/role/tg-centralis/role-id" {
|
||
|
|
capabilities = ["read"]
|
||
|
|
}
|