A module may hold several secrets from one provider, each a pair of its own

secrets: maps a requirement to several files under local names. Each local name is
its own need, its own pair credential (the pair is keyed on it: migration 0027),
its own file on the consumer, its own holder at the provider (the identity with the
local name after it) and rotates apart from the others. The plain shape is
unchanged and every existing row is the credential it was (novox/hq 04-ISSUES/069,
ADR 0094).
This commit is contained in:
2026-09-21 20:28:16 +02:00
parent f3bfc11565
commit 6ae4ae1dba
13 changed files with 567 additions and 142 deletions
+20 -17
View File
@@ -61,23 +61,26 @@ func sealedFor(m Manifest, needs []Needed, with Rendering) (map[string]string, e
sealed[name] = value
}
}
for _, to := range sortedKeys(m.Secrets) {
if _, taken := sealed[to]; taken {
// A module whose own secret and whose requirement share a name. Refused rather than
// settled by precedence: whichever won, the manifest would read as though the other
// had, and the file would hold the credential for the wrong thing while every check
// passed.
return nil, fmt.Errorf(
"%s has a secret of its own called %q and also requires %q, so a file saying "+
"${secret:%s} could mean either — rename one of them", m.Module, to, to, to)
}
for i := range needs {
// `For == m.Module`, not name alone: on a node with two modules requiring the same
// provision, both appear in `needs`, and matching by name would fill ${secret:X} with
// whichever came last — the other module's credential (novox/hq 04-ISSUES/022). The
// `secrets:`-map path already guards this way; the ${secret:…} placeholder path did not.
if needs[i].Name == to && needs[i].For == m.Module && needs[i].Sealed != "" {
sealed[to] = needs[i].Sealed
for _, to := range m.SecretRequirements() {
for _, file := range m.SecretFiles(to) {
key := SecretLocal(to, file.Local)
if _, taken := sealed[key]; taken {
// A module whose own secret and whose requirement share a name. Refused rather than
// settled by precedence: whichever won, the manifest would read as though the other
// had, and the file would hold the credential for the wrong thing while every check
// passed.
return nil, fmt.Errorf(
"%s has a secret of its own called %q and also requires %q, so a file saying "+
"${secret:%s} could mean either — rename one of them", m.Module, key, key, key)
}
for i := range needs {
// `For == m.Module`, not name alone: on a node with two modules requiring the same
// provision, both appear in `needs`, and matching by name would fill ${secret:X} with
// whichever came last — the other module's credential (novox/hq 04-ISSUES/022). And
// the local name, where the module keeps several (ADR 0094).
if needs[i].Name == to && needs[i].For == m.Module && needs[i].Local == file.Local && needs[i].Sealed != "" {
sealed[key] = needs[i].Sealed
}
}
}
}