A secret reaches a process as a file (ADR 0086, closes issue 041) #35
@@ -271,9 +271,9 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) {
|
||||
return nil, fmt.Errorf(
|
||||
"%s needs a secret called %q and none was made for it", m.Module, name)
|
||||
}
|
||||
first = append(first, map[string]any{
|
||||
first = append(first, ownedBy(m.SecretsOwner, map[string]any{
|
||||
"id": NeedID(name), "type": "file", "path": m.OwnSecrets[name], "sealed": sealed,
|
||||
})
|
||||
}))
|
||||
}
|
||||
// Operator-owned paths this module is granted use of (novox/hq ADR 0051). Written before
|
||||
// the module's own resources, and so before the container that mounts them: the host must
|
||||
@@ -320,10 +320,10 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) {
|
||||
// worse than none: something would read it and fail authenticating.
|
||||
continue
|
||||
}
|
||||
first = append(first, map[string]any{
|
||||
first = append(first, ownedBy(m.SecretsOwner, map[string]any{
|
||||
"id": SecretID(to), "type": "file", "path": m.Secrets[to],
|
||||
"sealed": found.Sealed,
|
||||
})
|
||||
}))
|
||||
}
|
||||
for _, to := range sortedKeys(m.Grants) {
|
||||
for _, g := range with.Grants {
|
||||
@@ -799,6 +799,14 @@ func keptFile(dir string, kept *KeptExport) (map[string]any, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ownedBy gives a secret file the owner the module named, when it named one (Manifest.SecretsOwner).
|
||||
func ownedBy(owner string, file map[string]any) map[string]any {
|
||||
if owner != "" {
|
||||
file["owner"] = owner
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// sortedKeys is map iteration made repeatable, which everything written to a machine needs.
|
||||
func sortedKeys[V any](m map[string]V) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
|
||||
@@ -297,6 +297,18 @@ type Manifest struct {
|
||||
// module, in a file anybody can read, for ever.
|
||||
OwnSecrets map[string]string `json:"own-secrets,omitempty"`
|
||||
|
||||
// SecretsOwner is who the files holding this module's secrets belong to on the machine —
|
||||
// `uid:gid`, or a name — when its process is not root.
|
||||
//
|
||||
// **A secret reaches a process as a file** (novox/hq ADR 0086), and a file the host writes at
|
||||
// 0600 as root is a file a container running as another account cannot read: the control
|
||||
// plane, `USER 65534` in a scratch image, crash-looped on `permission denied` the first time
|
||||
// its credentials were mounted instead of read from an env-file (which the daemon reads, as
|
||||
// root, on the host side — which is exactly why that shape hid the problem). Absent means
|
||||
// root, which is what a process that runs as root needs and what a process that does not
|
||||
// cannot use.
|
||||
SecretsOwner string `json:"secrets-owner,omitempty"`
|
||||
|
||||
// Keeps is where this module wants every operator-sealed secret in the mesh written — the
|
||||
// vault's field, and so far nobody else's (novox/hq ADR 0085, amended).
|
||||
//
|
||||
|
||||
@@ -70,3 +70,34 @@ func TestAnEnvFileWithoutASecretNeedsNothing(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// A module whose process is not root names who its secret files belong to, and every secret file
|
||||
// the composer writes for it carries that owner — the mounted file is readable where it is used.
|
||||
func TestSecretFilesCarryTheOwnerTheModuleNamed(t *testing.T) {
|
||||
m := aModuleWithAnEnvFileSecret("said")
|
||||
m.SecretsOwner = "65534:65534"
|
||||
out, err := declare(t, m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var seen bool
|
||||
for _, r := range out {
|
||||
if r["path"] != "/var/lib/app/token.secret" {
|
||||
continue
|
||||
}
|
||||
seen = true
|
||||
if r["owner"] != "65534:65534" {
|
||||
t.Errorf("the secret file is owned by %v", r["owner"])
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
t.Fatal("no secret file in the declaration")
|
||||
}
|
||||
m.SecretsOwner = ""
|
||||
out, _ = declare(t, m)
|
||||
for _, r := range out {
|
||||
if r["path"] == "/var/lib/app/token.secret" && r["owner"] != nil {
|
||||
t.Errorf("an owner was invented: %v", r["owner"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user