Give a bundle module's bound credential to the account its runtime runs as (hq issue 624)
mesh/merge-gate out of date: judged against trunk f3875a6b6019, the trunk is now 0a1dc5f93fcb; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk f3875a6b6019, the trunk is now 0a1dc5f93fcb; judged again on a recheck or when it is merged
mesh/delivery stopped: the pull request closed unmerged
mesh/merge-gate out of date: judged against trunk f3875a6b6019, the trunk is now 0a1dc5f93fcb; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk f3875a6b6019, the trunk is now 0a1dc5f93fcb; judged again on a recheck or when it is merged
mesh/delivery stopped: the pull request closed unmerged
A consumer's credential file was composed with the owner the module declared, root when it declared none. A module whose code is a bundle the node's runtime runs reads it as the operator's account, and a root-owned 0600 file is one it cannot read: the lab cloned the forge with no credential. The grant side was fixed the same way for issue 225; this is the consumer's side. A declared owner still wins.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package catalogue
|
||||
|
||||
import "testing"
|
||||
|
||||
// A consumer's credential is read by the consumer's own code, and that stopped being root for every
|
||||
// module whose code is a bundle the node's runtime runs (novox/hq issue 624, the consumer's side of
|
||||
// issue 225). The lab requires the package registry and reads its bound credential to clone the forge;
|
||||
// the credential file was composed with no owner, so root's and 0600, and the lab — running as the
|
||||
// operator's account — cloned with no credential and was refused, "could not read Username".
|
||||
|
||||
// aConsumerWithABundle is the lab's shape: tools in a bundle, a requirement, its credential in a file.
|
||||
func aConsumerWithABundle() Manifest {
|
||||
return Manifest{
|
||||
Module: "lab",
|
||||
Requires: []string{"npm-package-registry"},
|
||||
Secrets: map[string]string{"npm-package-registry": "/var/lib/lab/package-registry.secret"},
|
||||
Bundles: []Bundle{{Name: "code", Language: "typescript"}},
|
||||
}
|
||||
}
|
||||
|
||||
func consumerSecret(t *testing.T, m Manifest, account string) map[string]any {
|
||||
t.Helper()
|
||||
r := Resolution{Node: "shanks", Account: account, Modules: []Manifest{m},
|
||||
Needs: []Needed{{Name: "npm-package-registry", For: m.Module, From: "novox", Sealed: "c2VhbGVk"}}}
|
||||
out, err := r.Declaration(Rendering{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, res := range out {
|
||||
if res["type"] == "file" && res["path"] == "/var/lib/lab/package-registry.secret" {
|
||||
return res
|
||||
}
|
||||
}
|
||||
t.Fatalf("no credential file was composed: %v", out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestABundlesBoundCredentialBelongsToTheAccountItsRuntimeRunsAs(t *testing.T) {
|
||||
if got := consumerSecret(t, aConsumerWithABundle(), "jochen")["owner"]; got != "jochen" {
|
||||
t.Fatalf("the lab's credential belongs to %v; its tools run as %q, and a root-owned 0600 file is one "+
|
||||
"they cannot read", got, "jochen")
|
||||
}
|
||||
}
|
||||
|
||||
// A module that names its secrets' owner keeps it, and one with no bundle and no owner stays root's.
|
||||
func TestAConsumersDeclaredSecretsOwnerStillWins(t *testing.T) {
|
||||
m := aConsumerWithABundle()
|
||||
m.SecretsOwner = "65534:65534"
|
||||
if got := consumerSecret(t, m, "jochen")["owner"]; got != "65534:65534" {
|
||||
t.Errorf("a declared owner was replaced by %v", got)
|
||||
}
|
||||
m = aConsumerWithABundle()
|
||||
m.Bundles = nil
|
||||
if got, has := consumerSecret(t, m, "jochen")["owner"]; has {
|
||||
t.Errorf("a container consumer's credential was given the owner %v", got)
|
||||
}
|
||||
}
|
||||
@@ -730,7 +730,16 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
|
||||
// worse than none: something would read it and fail authenticating.
|
||||
continue
|
||||
}
|
||||
first = append(first, ownedBy(m.SecretsOwner, map[string]any{
|
||||
// **Read by the module's own code, so owned by whoever runs it** (novox/hq issue 624, the
|
||||
// consumer's side of issue 225): a module whose code is a bundle the node's runtime runs reads
|
||||
// its binding's credential as the operator's account, and a root-owned 0600 file is one it
|
||||
// cannot read — the lab cloned the forge with no credential and said nothing about why. A
|
||||
// module that names its secrets' owner keeps it.
|
||||
owner := m.SecretsOwner
|
||||
if owner == "" {
|
||||
owner = r.provisionsAs(m)
|
||||
}
|
||||
first = append(first, ownedBy(owner, map[string]any{
|
||||
"id": SecretID(SecretLocal(to, file.Local)), "type": "file", "path": file.Path,
|
||||
"sealed": found.Sealed,
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user