coordinator: domain-separate transit engine keys from workload secrets#2500
coordinator: domain-separate transit engine keys from workload secrets#2500sespiros wants to merge 1 commit into
Conversation
The transit engine derived its AES keys with DeriveWorkloadSecret("<version>_<name>"),
the same HKDF namespace that hands each pod its workload secret. A pod deployed with
WorkloadSecretID "0_vault_unsealing" thus received, in its own workload secret, the
exact key the transit engine uses to wrap OpenBao's master key. That defeats the
transit API's per-workload authorization on both directions: the pod can unwrap the
auto-unseal blob offline, and (since it holds the key) forge a blob the Coordinator
will decrypt, injecting an attacker-chosen master key on OpenBao's untrusted storage.
Derive transit keys from a dedicated seed and HKDF label so their keyspace can never
alias a workload secret. No decrypt-side fallback to the old derivation is provided on
purpose: honoring the legacy key would keep the forgery path open, since the server
cannot tell an original legacy blob from an attacker-forged one. This is a breaking
change for existing OpenBao auto-unseal deployments, which must migrate their master
key (seal-migration or re-init) onto the new key on upgrade.
Signed-off-by: Spyros Seimenis <sse@edgeless.systems>
burgerdev
left a comment
There was a problem hiding this comment.
Thanks for fixing this, lgtm!
As migration path, we could recommend starting a pod with the problematic 0_$ID to obtain the old sealing secret, then reseal using the transit secret engine. I don't think we need to keep a backwards-compatible implementation around.
| }, logger) | ||
| return | ||
| } | ||
| key, err := deriveEncryptionKey(r.Context(), guard, fmt.Sprintf("%d_%s", encReq.KeyVersion, workloadSecretID)) |
There was a problem hiding this comment.
Now that I think of it, I remember that we picked %d_%s because generated workloadSecretIDs don't contain an underscore: #1199 (comment). We could enforce that in manifest.Validate, but that would be even more breaking.
There was a problem hiding this comment.
Right! I didn't like that _ was essentially that load-bearing. I initially solved this by changing the HKDF info label but then I thought that a dedicated seed would split the two keyspaces even more cleanly.
The transit engine derived its AES keys with DeriveWorkloadSecret("_"), the same HKDF namespace that hands each pod its workload secret. A pod deployed with WorkloadSecretID "0_vault_unsealing" thus received, in its own workload secret, the exact key the transit engine uses to wrap OpenBao's master key. That defeats the transit API's per-workload authorization on both directions: the pod can unwrap the auto-unseal blob offline, and (since it holds the key) forge a blob the Coordinator will decrypt, injecting an attacker-chosen master key on OpenBao's untrusted storage.
Derive transit keys from a dedicated seed and HKDF label so their keyspace can never alias a workload secret. No decrypt-side fallback to the old derivation is provided on purpose: honoring the legacy key would keep the forgery path open, since the server cannot tell an original legacy blob from an attacker-forged one. This is a breaking change for existing OpenBao auto-unseal deployments, which must migrate their master key (seal-migration or re-init) onto the new key on upgrade.
Fixes CON-201
TODO
Decide on rollout/backwards compatibility as this is a breaking change!This is now tagged as a breaking change for the release notes.