diff --git a/internal/catalogue/declaration.go b/internal/catalogue/declaration.go index 14333ba..03846bd 100644 --- a/internal/catalogue/declaration.go +++ b/internal/catalogue/declaration.go @@ -508,7 +508,17 @@ func (r Resolution) compose(with Rendering, owner map[string]string, return nil, fmt.Errorf( "%s needs a secret called %q and none was made for it", m.Module, name) } - first = append(first, ownedBy(m.SecretsOwner, map[string]any{ + // The runtime's credential belongs to the account the runtime runs as (novox/hq ADR 0175, + // to-be 38 WP3): its process is composed `user: ` where the node has one, and a + // root-owned 0600 file is one that process cannot read. Composed here rather than said in + // the manifest, because a manifest cannot say ${machine:account} safely — a node with no + // account has nothing to resolve it to, and then the runtime runs as root and the file + // stays root's. + owner := m.SecretsOwner + if m.Module == RuntimeModule && r.Account != "" { + owner = r.Account + } + first = append(first, ownedBy(owner, map[string]any{ "id": NeedID(name), "type": "file", "path": m.OwnSecrets[name].Path, "sealed": sealed, })) } diff --git a/internal/catalogue/runtime_test.go b/internal/catalogue/runtime_test.go index 8dc73b6..efac9aa 100644 --- a/internal/catalogue/runtime_test.go +++ b/internal/catalogue/runtime_test.go @@ -160,6 +160,11 @@ func TestTheMachineRunsOneRuntimeLoadingEveryDeliveredBundle(t *testing.T) { if env[RuntimeOperatorAccount] != "ops" || env[RuntimeOperatorHome] != "/home/ops" || process["user"] != "ops" { t.Errorf("the operator is not handed to the runtime: %v as %v", env, process["user"]) } + // The credential the process reads belongs to the account it runs as, or it could not read it + // (to-be 38 WP3); other modules' secrets are left as their manifests say. + if credential := fileNamed(out, RuntimeModule+"."+NeedID("broker")); credential == nil || credential["owner"] != "ops" { + t.Errorf("the runtime's credential is not the account's to read: %v", credential) + } restarts := fmt.Sprint(process["restart-on"]) for _, want := range []string{"nftables." + BundleID("tools"), "showcase." + BundleID("code"), RuntimeModule + "." + NeedID("broker")} { if !strings.Contains(restarts, want) { @@ -185,6 +190,9 @@ func TestTheMachineRunsOneRuntimeLoadingEveryDeliveredBundle(t *testing.T) { if _, set := process["user"]; set { t.Error("a user was set on a machine with no account") } + if credential := fileNamed(out, RuntimeModule+"."+NeedID("broker")); credential == nil || credential["owner"] != nil { + t.Errorf("the runtime's credential was given an owner on a machine with no account: %v", credential) + } }) t.Run("a runtime module built wrong is refused by name", func(t *testing.T) {