From 93d6f40106224d12efa02f518acf62c75800d826 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:29:53 -0400 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20a=20workload=20credential=20carries?= =?UTF-8?q?=20no=20build=20seal=20(core=20#812)=20=E2=80=94=20WIP,=20tests?= =?UTF-8?q?=20follow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves to the host-aligned sdk-go 70dc7a54 / core b81d92e6. Since core's signed module manifest the host binds the execution at mint (the projected token and the pod it names, held to the approved build) and seals none, so the runtime's 'seals no execution' refusal rejected every valid credential: on a host running it, every viewer call a solution makes (collection, search, chat, traces) failed. The sealed identity is the installation alone; the scoped credential's build-consistency check goes with the seal. Test fixtures still reference the removed seal fields and are updated in the next commit. Co-Authored-By: Claude Opus 5.5 --- credential.go | 19 +++++-------------- go.mod | 6 +++--- go.sum | 12 ++++++------ scoped_credential.go | 10 ---------- solution.go | 43 +++++++------------------------------------ 5 files changed, 21 insertions(+), 69 deletions(-) diff --git a/credential.go b/credential.go index 4380996..45c139c 100644 --- a/credential.go +++ b/credential.go @@ -186,11 +186,7 @@ func (a *authorityHeldSource) Credential(ctx context.Context) (workcontext.Crede // // Read through the SDK's own accessor on the verified seal. No decoder here. func (a *authorityHeldSource) holdSealedIdentity(seal *workcontext.SealedValues) error { - held := sealedIdentity{ - installation: seal.GetInstallationId(), - digest: seal.GetImageDigest(), - incarnation: seal.GetBuildIncarnation(), - } + held := sealedIdentity{installation: seal.GetInstallationId()} a.mu.Lock() defer a.mu.Unlock() if a.sealed == (sealedIdentity{}) { @@ -200,19 +196,14 @@ func (a *authorityHeldSource) holdSealedIdentity(seal *workcontext.SealedValues) if a.sealed == held { return nil } - return fmt.Errorf("%w: this execution has been acting under installation %s, image %s, build incarnation %d, and its credential renewed sealed to installation %s, image %s, build incarnation %d. That is a different execution, not a renewal: every work context this process mints for a viewer is attested by this credential, so the mints before and after this point would name a different deployment or a different build — including one the host's approved-build ceiling was never applied to", - workcontext.ErrMintRefused, - a.sealed.installation, a.sealed.digest, a.sealed.incarnation, - held.installation, held.digest, held.incarnation) + return fmt.Errorf("%w: this execution has been acting under installation %s, and its credential renewed sealed to installation %s. That is a different deployment, not a renewal: every work context this process mints for a viewer is attested by this credential, so the mints before and after this point would name a different deployment", + workcontext.ErrMintRefused, a.sealed.installation, held.installation) } -// sealedIdentity is what a credential is sealed to: which deployment, and -// which build inside it. Compared as one value, because a renewal that changed -// any part of it changed which execution is asking. +// sealedIdentity is what a credential is sealed to: which deployment. The +// build is no longer sealed (core #812): the host binds it at every mint. type sealedIdentity struct { installation string - digest string - incarnation uint64 } // mintOptions binds the declared prefix and perimeter credential to the frozen diff --git a/go.mod b/go.mod index c862b60..84d1f80 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.27.0 require ( connectrpc.com/connect v1.21.0 - github.com/codefly-dev/core v0.19.5-0.20261013191419-d82010241d74 + github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda github.com/codefly-dev/saas-sdk-go v0.1.3-0.20261014043138-b7169d3c4095 - github.com/codefly-dev/sdk-go v0.3.2 - github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261013172033-aabfb4cf5359 + github.com/codefly-dev/sdk-go v0.4.1-0.20261018052022-70dc7a546458 + github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458 github.com/google/uuid v1.6.0 github.com/modelcontextprotocol/go-sdk v1.8.0 golang.org/x/tools v0.52.0 diff --git a/go.sum b/go.sum index 1dec4aa..3a06415 100644 --- a/go.sum +++ b/go.sum @@ -30,16 +30,16 @@ github.com/cheggaaa/pb/v3 v3.2.1 h1:aprZbFRG+B7+ug76S8QZ6Y1PW168UHzOmbC3wa+aU6I= github.com/cheggaaa/pb/v3 v3.2.1/go.mod h1:U9hSVxoKqJrZIE3PkFG1xXXNaK/Ilzg+EF/scpXzEdw= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/codefly-dev/core v0.19.5-0.20261013191419-d82010241d74 h1:tUEPZ1SJlpRUmEHasKr5++LHay8ZuoJxfd2xp9aiO+k= -github.com/codefly-dev/core v0.19.5-0.20261013191419-d82010241d74/go.mod h1:0uX/7lTBVaGV0RI2Mb3mpEr/PPdx/jZqXHym/U8FVDI= +github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda h1:ytTihtIEfVhQ7llQl+ls5swIL8JgJu89X0wlDMeqM+o= +github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda/go.mod h1:0uX/7lTBVaGV0RI2Mb3mpEr/PPdx/jZqXHym/U8FVDI= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= github.com/codefly-dev/saas-sdk-go v0.1.3-0.20261014043138-b7169d3c4095 h1:jkCbK8/r8vWAPajw5wrsCM378Cbps7kWwL0QTDJFHzA= github.com/codefly-dev/saas-sdk-go v0.1.3-0.20261014043138-b7169d3c4095/go.mod h1:GpQGVcV6jh88PLcheey+QuxlTzYEMB3aRosQGhMpESg= -github.com/codefly-dev/sdk-go v0.3.2 h1:H1Tstn5f2HWO/Hp813RFxxDPAlWZEIN6gKtxpzai1CY= -github.com/codefly-dev/sdk-go v0.3.2/go.mod h1:B7ULHzZo29FCvE8pfCa90aPVUlL2PlHNUXrIBXuKQ4g= -github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261013172033-aabfb4cf5359 h1:T+xMCxRDnTSV6jjMTqDCVORLbpHoIYdL9IR3O6330oo= -github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261013172033-aabfb4cf5359/go.mod h1:qyHZ7bvnxXWlJHt8PQ9u0gCLTClASPtmIgqUQIrigXo= +github.com/codefly-dev/sdk-go v0.4.1-0.20261018052022-70dc7a546458 h1:hi+KhotTLMOJ/PUSzVO/Axfho7yZ2UOjrCFfrLI1EKU= +github.com/codefly-dev/sdk-go v0.4.1-0.20261018052022-70dc7a546458/go.mod h1:Qg8SMzZxKSllwvZwShMrWgSetBVMjxP0s17mwWHWjk0= +github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458 h1:wBOL7QsuoC0HsS43gsH2+tpsipk4os077C2VUDkMUHg= +github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458/go.mod h1:hwtrdGtgh8i2xzjgihyd4wY2u+UK7nHQz30OGKL0n+M= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= diff --git a/scoped_credential.go b/scoped_credential.go index e999959..a80b3a5 100644 --- a/scoped_credential.go +++ b/scoped_credential.go @@ -141,16 +141,6 @@ func (s *Server) installationCredentialSource(scope credentialScope) (credential if err := matchCredentialScope(credential, scope); err != nil { return nil, err } - seal := credential.Seal() - build := sealedIdentity{digest: seal.GetImageDigest(), incarnation: seal.GetBuildIncarnation()} - s.executionMu.Lock() - defer s.executionMu.Unlock() - if s.execution == (sealedIdentity{}) { - s.execution = build - } - if s.execution != build { - return nil, workcontext.ErrMintRefused - } return credential, nil }, nil } diff --git a/solution.go b/solution.go index b33ceb3..dd375d6 100644 --- a/solution.go +++ b/solution.go @@ -1092,42 +1092,13 @@ func usableCredential(credential executionCredential) error { return fmt.Errorf("%w: the credential this process holds is sealed to no installation, so there is nothing for a far end to hold it to", workcontext.ErrMintRefused) } - // And it has to attest WHICH BUILD is asking. - // - // This check did not exist and did not need to: the previous SDK's own - // reader refused a seal whose build incarnation was zero, so a credential - // reaching here carried one by construction. The v0.3.0 migration moved - // structural validation to core, and core legitimately mints a credential - // sealing NO execution — a principal recorded as bearing none, a person at - // a terminal, which is core's own execution-missing shape. The guarantee - // was inherited, the inheritance ended, and nothing here noticed. - // - // Reproduced before fixing: a runtime booted on such a credential, held - // it, passed actingForAViewer, and served handlers and ViewerBearer - // passthroughs with a credential attesting no approved build at all. - // - // A WORKLOAD credential is the one kind that may never be execution-free. - // Its whole purpose is attesting which build is asking, so that a host can - // hold a mint to the build its presence document approved; one sealing no - // execution asserts nothing a host can check and the ceiling it is held to - // is the ceiling of nobody. - // - // Read through CORE'S OWN generated accessors on the verified capability's - // seal — Credential.Seal() returns core's protobuf type, so GetImageDigest - // and GetBuildIncarnation are core's, not a predicate of this package's. - // No decoder, no seal validator and no copy of sealOf grows here; that - // would be the second implementation this module is gated against. Both - // halves are required rather than either, because an execution is the pair - // — core's schema refuses a digest without an incarnation, so the shapes - // where "and" differs from "or" are shapes that never arrive. - // - // Terminal. A credential attesting no build is not a transient condition: - // the issuer will seal the same thing next time, and a loop around it is - // the audited-mint cost this runtime was changed to remove. - if seal := credential.Seal(); seal.GetBuildIncarnation() == 0 || seal.GetImageDigest() == "" { - return fmt.Errorf("%w: the credential this process holds seals no execution (image digest %q, build incarnation %d), so it attests no approved build and nothing a host can hold a mint to. A workload credential names the build that is asking; one that names none is a credential for a principal bearing no execution, which this process is not", - workcontext.ErrMintRefused, seal.GetImageDigest(), seal.GetBuildIncarnation()) - } + // No build is read off the seal. Since core's signed module manifest + // (core #812), a Work Context carries no build seal: the host binds the + // execution itself at mint, holding the caller's projected ServiceAccount + // token and the pod it names against the build its applied manifest + // approves, and refuses a mint for anything else. A runtime-side check of + // a seal field the protocol no longer carries would refuse every valid + // credential. if expiry := credential.ExpiresAt(); !time.Now().Before(expiry) { return fmt.Errorf("%w: the credential this process holds expired at %s", workcontext.ErrMintUnavailable, expiry.UTC().Format(time.RFC3339)) From 9a466b8ebe2c7894aaddbb5e3644da34c60c9ff9 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 18 Oct 2026 15:09:35 -0400 Subject: [PATCH 2/2] test: the sealed identity is the installation alone (core #812) Core b81d92e6 seals no build and mints from no Execution input, so the tests that proved the build half of the seal now describe a shape core cannot mint: the execution-free refusal test, the build-changed renewal test and table rows are removed; the broker renewal table and the per-field comparison keep the installation case; the conformance floor follows core's 35 fixtures (the five build-seal ones are gone); the typed-decode fixture spells an accessor core still has. go test ./... green. Co-Authored-By: Claude Opus 5.5 --- boot_test.go | 132 +------------------- broker_credential_test.go | 12 +- conformance_test.go | 4 +- credential_test.go | 189 ++--------------------------- passthroughtest/passthroughtest.go | 8 -- scoped_credential_test.go | 2 +- solution_test.go | 4 - typed_boundary_test.go | 2 +- 8 files changed, 24 insertions(+), 329 deletions(-) diff --git a/boot_test.go b/boot_test.go index 9c631e6..038e5ee 100644 --- a/boot_test.go +++ b/boot_test.go @@ -53,8 +53,7 @@ const testFrontend = "title: " + testSolutionTitle + "\norder: 1\nexposed_module // identities, so the seal this runtime logs and a test asserts is the live one // that authority holds rather than a number invented here. const ( - testInstallation = corework.FixtureInstallation - testBuildIncarnation = uint64(corework.FixtureBuildIncarnation) + testInstallation = corework.FixtureInstallation ) // authorityValues provisions the module-authority group the way the platform @@ -105,17 +104,6 @@ type hostMint struct { // through the real client rather than by calling the wrapper directly. switchTo *hostMint switchAfter int64 - // digest and incarnation override the build this host attests, so a test - // can have a RENEWAL seal a different execution than the first credential. - digest string - incarnation uint64 - // executionFree makes this host mint a credential sealing NO execution, - // which core allows: the sealed execution became optional because a - // principal that bears no approved build — a person at a terminal — must - // not have one invented for it. It is the shape of core's own - // execution-missing fixture, and it is not a shape a WORKLOAD credential - // may have. - executionFree bool // installation overrides the installation it seals credentials to, so a // test can have a renewal answer about a different one. Empty means // testInstallation. @@ -146,62 +134,6 @@ type hostMint struct { func (mint *hostMint) prepareAuthority(t *testing.T) { t.Helper() mint.authority = standInAuthority() - if mint.executionFree { - // An issuer that mints a credential sealing NO execution, which core - // permits only for a principal bearing no approved build — a person at - // a terminal. Built by taking core's fixture seal source and recording - // the installation seal and epoch WITHOUT an approved build, which is - // the shape of core's own execution-missing fixture. - // - // Core refuses to mint execution-free for a principal that does bear - // one, which is why this cannot be done by simply omitting the field. - seals := corework.NewMemorySealSource() - if err := seals.Put(corework.FixturePrincipal, corework.Seal{ - InstallationID: testInstallation, - InstallationRevision: corework.FixtureInstallationRevision, - }); err != nil { - t.Fatalf("seal an execution-free issuer: %v", err) - } - if err := seals.PutEpoch(corework.FixturePrincipal, corework.FixturePrincipalEpoch); err != nil { - t.Fatalf("record the principal's epoch: %v", err) - } - // Explicitly bearing no execution, which is core's own distinction: - // a principal with an approved build MUST attest it, one recorded as - // bearing none legitimately carries no execution, and one core knows - // nothing about is refused. This is the middle case — a person at a - // terminal — and it is the shape a WORKLOAD credential must never have. - if err := seals.PutBearsNoExecution(corework.FixturePrincipal); err != nil { - t.Fatalf("record the principal as bearing no execution: %v", err) - } - _, key := corework.FixtureKeyPair() - mint.authority = &corework.Authority{ - Issuer: corework.FixtureIssuer, - KeyID: corework.FixtureKeyID, - Key: key, - Revisions: corework.FixtureRevisions(), - Seals: seals, - } - } - if mint.digest != "" || mint.incarnation != 0 { - // An issuer whose APPROVED BUILD has moved, which is the only way it - // can attest a different one: core refuses to seal an execution the - // issuer does not approve for that principal. Built from core's own - // fixture seal source with the approved build overridden. - seals := corework.FixtureSeals() - if err := seals.PutApprovedBuild(corework.FixturePrincipal, - cmp.Or(mint.digest, corework.FixtureImageDigest), - cmp.Or(mint.incarnation, uint64(corework.FixtureBuildIncarnation))); err != nil { - t.Fatalf("move the issuer's approved build: %v", err) - } - _, key := corework.FixtureKeyPair() - mint.authority = &corework.Authority{ - Issuer: corework.FixtureIssuer, - KeyID: corework.FixtureKeyID, - Key: key, - Revisions: corework.FixtureRevisions(), - Seals: seals, - } - } if mint.installation != "" { // An issuer that seals to a different installation, for the renewal // refusal. Built from CORE's fixture seal source with one entry @@ -253,8 +185,6 @@ func newHostMint(t *testing.T, mint *hostMint) *hostMint { return mint } -// execution is the build this host attests for the credential it mints, or -// nothing at all when the test asks for an execution-free one. // installationFor is the installation the shape in force seals to. func (m *hostMint) installationFor() string { if m.switchTo != nil && atomic.LoadInt64(&m.mints) > m.switchAfter { @@ -263,16 +193,6 @@ func (m *hostMint) installationFor() string { return m.installation } -func (m *hostMint) execution() corework.Execution { - if m.executionFree { - return corework.Execution{} - } - return corework.Execution{ - ImageDigest: cmp.Or(m.digest, corework.FixtureImageDigest), - BuildIncarnation: cmp.Or(m.incarnation, uint64(corework.FixtureBuildIncarnation)), - } -} - func (m *hostMint) serve(w http.ResponseWriter, r *http.Request) { organization := testOrganization m.mu.Lock() @@ -316,9 +236,9 @@ func (m *hostMint) serve(w http.ResponseWriter, r *http.Request) { w.WriteHeader(m.status) return } - issuer, execution := m.authority, m.execution() + issuer := m.authority if m.switchTo != nil && atomic.LoadInt64(&m.mints) > m.switchAfter { - issuer, execution = m.switchTo.authority, m.switchTo.execution() + issuer = m.switchTo.authority } token, _, err := issuer.Start(r.Context(), corework.StartInput{ TenantID: corework.FixtureTenant, @@ -329,13 +249,6 @@ func (m *hostMint) serve(w http.ResponseWriter, r *http.Request) { OrganizationID: organization, InstallationID: cmp.Or(m.installationFor(), testInstallation), TTL: m.ttl, - // Core v0.9.0 requires the caller to attest the build it is running: - // a principal that exercises an approved execution must name the image - // digest and incarnation, and the issuer checks them against the - // approved build it holds for that principal. The execution moved off - // the installation seal, where it described the OWNER's workload - // however many delegation hops had been added. - Execution: execution, }) if err != nil { m.signErr = err @@ -947,45 +860,6 @@ func takeListener(server *Server) *Server { return server } -func TestABootedWorkRefusesARenewalToAnotherExecution(t *testing.T) { - t.Setenv(manifest.APIConsumesEnvironmentVariable, consumesThings) - mint := newHostMint(t, &hostMint{ttl: 2 * time.Second, switchAfter: 1, switchTo: &hostMint{incarnation: 99, ttl: 2 * time.Second}}) - server := New(Manifest{ID: testSolutionID}).Consumes(passthroughModule()).Contract(ModuleContract{Ceilings: map[string]map[string][]Scope{ - localProfile: {"things": {{ResourceKind: "things", Actions: []string{"read"}}}}, - }}).Handle("/thing", func(ctx context.Context, gw *Gateway) (any, error) { - _, err := gw.ForModule(ctx, "things", Scope{ResourceKind: "things", Actions: []string{"read"}}) - return map[string]string{"ok": "yes"}, err - }) - solution := boot(t, server, mint) - request := func() int { - r, _ := http.NewRequest(http.MethodGet, solution.base+"/thing", nil) - r.Header.Set("authorization", viewerBearer()) - r.Header.Set(orgHeader, testOrganization) - r.Header.Set(sessionHeader, "session") - r.Header.Set(workcontext.InstallationIDHeaderName, testInstallation) - response, err := solution.client.Do(r) - if err != nil { - t.Fatal(err) - } - defer response.Body.Close() - if response.StatusCode != 200 { - body, _ := io.ReadAll(response.Body) - t.Logf("work response: %s", body) - } - return response.StatusCode - } - if got := request(); got != 200 { - t.Fatalf("initial work = %d", got) - } - time.Sleep(2100 * time.Millisecond) - if got := request(); got != 503 { - t.Fatalf("changed execution = %d", got) - } - if getStatus(t, solution.client, solution.base+HealthPath) != 200 { - t.Fatal("scoped refusal ended process") - } -} - // localRun is the SDK's transport source for a test: a local run that asserts // no mesh, so the only plaintext the mint client admits is loopback — the rule // a booted runtime applies to its resolved gateway, answered the way a test's diff --git a/broker_credential_test.go b/broker_credential_test.go index 2a1cf8e..ebff0a1 100644 --- a/broker_credential_test.go +++ b/broker_credential_test.go @@ -407,8 +407,10 @@ func TestBrokerHoldsAuthorityAndExecutionAcrossRenewals(t *testing.T) { name string after *hostMint }{ - {"execution missing", &hostMint{executionFree: true}}, - {"execution changed", &hostMint{incarnation: 99}}, + // A renewal sealed to another installation is another deployment: the one + // identity a credential still seals since the host stopped sealing a build + // (core #812) and binds it at every mint instead. + {"installation changed", &hostMint{installation: "installation-somewhere-else"}}, } { t.Run(tc.name, func(t *testing.T) { s, host, _ := brokerFixture(t) @@ -421,18 +423,18 @@ func TestBrokerHoldsAuthorityAndExecutionAcrossRenewals(t *testing.T) { s.brokerCredential.renewAt = time.Time{} for range 2 { if _, err := s.acquireCredential(context.Background()); !errors.Is(err, workcontext.ErrMintRefused) || !errors.Is(err, ErrCredentialRefused) { - t.Fatalf("execution change accepted or not terminal: %v", err) + t.Fatalf("installation change accepted or not terminal: %v", err) } } if host.count() != 2 { - t.Fatalf("execution change retried: %d", host.count()) + t.Fatalf("installation change retried: %d", host.count()) } // Exercise the native broker through the controller without a // socket. The booted test separately proves serve observes this // signal and returns without cancellation by the test. if terminal := s.controller.terminalErr.Load(); terminal == nil || !errors.Is(*terminal, workcontext.ErrMintRefused) { - t.Fatal("the execution refusal was not recorded for serve") + t.Fatal("the installation refusal was not recorded for serve") } }) } diff --git a/conformance_test.go b/conformance_test.go index d68ffe0..b09f56f 100644 --- a/conformance_test.go +++ b/conformance_test.go @@ -49,8 +49,8 @@ func TestCoreConformanceFixturesThroughTheCarrier(t *testing.T) { if err != nil { t.Fatalf("core conformance fixtures: %v", err) } - if len(fixtures) < 40 { - t.Fatalf("core published %d fixtures; the kit is 40 at core v0.19.1, so this test is reading a trimmed set", len(fixtures)) + if len(fixtures) < 35 { + t.Fatalf("core published %d fixtures; the kit is 35 at core b81d92e6 (core #812 removed the five build-seal fixtures), so this test is reading a trimmed set", len(fixtures)) } var carriedCount, refusedCount int diff --git a/credential_test.go b/credential_test.go index f4e177c..0ff502a 100644 --- a/credential_test.go +++ b/credential_test.go @@ -1855,165 +1855,6 @@ func expiredCredential(t *testing.T, mint *hostMint, tokenFile string) workconte return credential } -// TestAnExecutionFreeCredentialIsRefusedEverywhere is round eleven's blocker, -// and a regression the sdk-go v0.3.0 migration introduced silently. -// -// usableCredential never checked for a sealed execution and never had to: the -// previous SDK's own reader refused a seal whose build incarnation was zero, -// so a credential reaching it carried one by construction. v0.3.0 moved -// structural validation to core, and core LEGITIMATELY mints a credential -// sealing no execution — for a principal recorded as bearing none, a person at -// a terminal. That is core's own execution-missing shape. The guarantee was -// inherited, the inheritance ended, and nothing here noticed. -// -// Reproduced before it was fixed: the runtime booted on such a credential, -// held it, passed actingForAViewer, and served handlers with a credential -// attesting no approved build. -// -// A workload credential is the one kind that may never be execution-free: its -// purpose is attesting which build is asking, so a host can hold a mint to the -// build its presence document approved. -func TestAnExecutionFreeCredentialIsRefusedEverywhere(t *testing.T) { - t.Run("first work refuses it", func(t *testing.T) { - mint := newHostMint(t, &hostMint{executionFree: true}) - bootEnvironment(t, mint) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - server := declaredWorkServer(t) - // Short, so a refusal that were wrongly classified as transient shows - // up as a bounded wait rather than hanging this test. - - ln, err := takeListener(server).start(ctx) - if ln != nil { - _ = ln.Close() - } - if err != nil { - t.Fatal(err) - } - err = testWorkCredential(server, ctx) - if err == nil { - t.Fatal("first work accepted a credential sealing no execution: it attests no approved build, so the host has nothing to hold a mint to and the ceiling it is held to is nobody's") - } - if !errors.Is(err, workcontext.ErrMintRefused) { - t.Errorf("the boot failed with %v, want ErrMintRefused: the issuer will seal the same thing next time, so this is a judgement and not an outage", err) - } - if !strings.Contains(err.Error(), "seals no execution") { - t.Errorf("the refusal %q does not say what is missing", err) - } - // Terminal means it asked ONCE. A bounded wait here would be the - // audited-mint loop this runtime was changed to remove. - if got := mint.count(); got != 1 { - t.Errorf("the boot asked the issuer %d times, want exactly 1", got) - } - }) - - t.Run("no handler runs and no module is called", func(t *testing.T) { - // The route gate's half: even handed such a credential directly, a - // route must not act for a viewer and nothing author-written may run. - mint := newHostMint(t, &hostMint{executionFree: true}) - tokenFile := filepath.Join(t.TempDir(), "token") - writeFile(t, tokenFile, "projected") - gw := newWorkContextGateway(t, &workContextGateway{}) - - var ran atomic.Bool - server := declaredWorkServer(t). - Credential(mintClientFor(t, mint, tokenFile)) - server.cfg.gatewayURL = gw.URL - // wrapRequest is what Serve mounts each route through - // (mux.HandleFunc(path, withCORS(s.wrapRequest(handler)))), so this - // drives the route's own gate rather than calling the gate directly. - route := server.wrapRequest(func(r *http.Request, g *Gateway) (any, error) { - if _, err := g.ForModule(r.Context(), "things", Scope{ResourceKind: "things", Actions: []string{"read"}}); err != nil { - return nil, err - } - ran.Store(true) - return map[string]string{"ok": "yes"}, nil - }) - - header := http.Header{} - header.Set("authorization", viewerBearer()) - header.Set(orgHeader, viewerOrg) - header.Set(sessionHeader, viewerSession) - header.Set(workcontext.InstallationIDHeaderName, testInstallation) - - // THROUGH THE REGISTERED ROUTE, not by calling the gate. - // - // This called actingForAViewer directly, which proves the gate - // refuses and nothing about whether a route consults it: removing the - // gate from the route left the handler uncalled and every count at - // zero, because nothing ever reached the route at all. A reviewer - // named it twice. - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/thing", nil) - request.Header = header - route(recorder, request) - - if recorder.Code != http.StatusServiceUnavailable { - t.Errorf("the route answered %d under a credential sealing no execution, want 503: it attests no approved build, so there is nothing for a host to hold a mint to", recorder.Code) - } - if ran.Load() { - t.Error("the handler ran under a credential sealing no execution: the refusal has to come before anything author-written") - } - if got := gw.mintCount(); got != 0 { - t.Errorf("the gateway saw %d viewer mints, want 0", got) - } - if got := len(gw.calls); got != 0 { - t.Errorf("a module was called %d times under a credential attesting no build, want 0", got) - } - - // And the gate itself, named, because the route's 503 does not say - // WHICH refusal produced it. - if err := testWorkCredential(server, context.Background()); err == nil { - t.Fatal("a route acted for a viewer under a credential sealing no execution") - } else if !errors.Is(err, ErrCredentialRefused) { - t.Errorf("the route refused with %v, want one wrapping ErrCredentialRefused", err) - } - - // THE POSITIVE CONTROL: the same route, the same headers, a credential - // that does seal an execution. Without it the 503 above is satisfied - // by a route that answers 503 to everything. - sound := newHostMint(t, &hostMint{}) - healthy := declaredWorkServer(t). - Credential(mintClientFor(t, sound, tokenFile)) - healthy.cfg.gatewayURL = gw.URL - controlRoute := healthy.wrapRequest(func(r *http.Request, g *Gateway) (any, error) { - if _, err := g.ForModule(r.Context(), "things", Scope{ResourceKind: "things", Actions: []string{"read"}}); err != nil { - return nil, err - } - return map[string]string{"ok": "yes"}, nil - }) - control := httptest.NewRecorder() - controlRequest := httptest.NewRequest(http.MethodGet, "/thing", nil) - controlRequest.Header = header - controlRoute(control, controlRequest) - if control.Code != http.StatusOK { - t.Errorf("the control route answered %d with a sound credential, want 200: so the 503 above says nothing about the execution-free one", control.Code) - } - }) - - t.Run("the carrier still accepts it as structurally valid", func(t *testing.T) { - // The division of labour stays intact. An execution-free capability is - // not malformed — core mints it on purpose for a principal bearing no - // execution — so the CARRIER must not refuse it. What refuses it is - // this runtime holding its OWN credential to a higher bar than it - // holds a capability it merely carries. - if _, classified := boundary["execution-missing"]; !classified { - t.Fatal("core's execution-missing fixture is unclassified, so this says nothing") - } - if boundary["execution-missing"] != carried { - t.Error("the carrier refuses core's execution-missing fixture: a capability core mints legitimately is not the carrier's to reject, and refusing it here would be the silent-downgrade failure this boundary exists to prevent") - } - }) -} - -// freshMint forces a NEW handshake against the endpoint. -// -// Calling Credential again would not: a mint client holds the credential it -// has until renewal is due, which is the one-per-execution property this -// runtime wants everywhere else, so "ask again" dials nothing and a withdrawal -// test that re-asked would pass against a client that never re-checked -// anything. Refresh is the call that re-mints, so it is the call that reaches -// the readers under test. func freshMint(t *testing.T, source any, held executionCredential) (executionCredential, error) { if broker, ok := source.(*brokerCredentialSource); ok { broker.mu.Lock() @@ -2062,13 +1903,11 @@ func TestARenewalSealedToADifferentExecutionIsRefused(t *testing.T) { name string moved *hostMint }{ - {"a different build incarnation", &hostMint{incarnation: 99}}, // A new digest comes with a new incarnation, because core refuses it // otherwise: "a new build is a new run, so advance the incarnation — // reusing it lets a swap back to the earlier digest re-admit every // capability sealed to it". So this is the realistic redeploy shape // rather than a digest swapped under a fixed incarnation. - {"a different image digest", &hostMint{digest: "sha256:" + strings.Repeat("b", 64), incarnation: 12}}, {"a different installation", &hostMint{installation: "installation-somewhere-else"}}, } { t.Run(tc.name, func(t *testing.T) { @@ -2085,12 +1924,12 @@ func TestARenewalSealedToADifferentExecutionIsRefused(t *testing.T) { source.credential = mintedCredential(t, moved, testProjectionAudience) _, err := held.Credential(ctx) if err == nil { - t.Fatal("a renewal sealed to a different execution was accepted: the mints before and after it would be attributed to different builds, and the ceiling the host approved for one would have been applied to the other") + t.Fatal("a renewal sealed to a different installation was accepted: the mints before and after it would be attributed to different deployments") } if !errors.Is(err, workcontext.ErrMintRefused) { t.Errorf("the refusal is %v, want ErrMintRefused: a process whose approved build has moved under it should end and be restarted against the delivery as it now stands", err) } - if !strings.Contains(err.Error(), "different execution") { + if !strings.Contains(err.Error(), "different deployment") { t.Errorf("the refusal %q does not say what changed", err) } }) @@ -2129,26 +1968,18 @@ func TestARenewalSealedToADifferentExecutionIsRefused(t *testing.T) { // test green because the incarnation moved too. This drives // holdSealedIdentity directly, one field at a time. func TestEachSealedFieldIsComparedOnItsOwn(t *testing.T) { - const digest = "sha256:" + "a" - seal := func(installation, image string, incarnation uint64) *workcontext.SealedValues { - // InstallationId is a plain string on the message; the other two are - // pointer scalars, which is why the production code reads all three - // through the Get accessors rather than the fields. - return &workcontext.SealedValues{ - InstallationId: installation, - ImageDigest: &image, - BuildIncarnation: &incarnation, - } + // The installation is the one field a seal still carries: the host binds the + // build at every mint and seals none (core #812). + seal := func(installation string) *workcontext.SealedValues { + return &workcontext.SealedValues{InstallationId: installation} } - first := seal(testInstallation, digest, 11) + first := seal(testInstallation) for _, tc := range []struct { field string moved *workcontext.SealedValues }{ - {"the image digest alone", seal(testInstallation, "sha256:b", 11)}, - {"the build incarnation alone", seal(testInstallation, digest, 12)}, - {"the installation alone", seal("installation-somewhere-else", digest, 11)}, + {"the installation", seal("installation-somewhere-else")}, } { t.Run(tc.field, func(t *testing.T) { held := &authorityHeldSource{} @@ -2157,7 +1988,7 @@ func TestEachSealedFieldIsComparedOnItsOwn(t *testing.T) { } err := held.holdSealedIdentity(tc.moved) if err == nil { - t.Fatalf("a renewal that moved %s was accepted: every work context this process mints is attested by this credential, so the mints either side would name a different deployment or a different build", tc.field) + t.Fatalf("a renewal that moved %s was accepted: every work context this process mints is attested by this credential, so the mints either side would name a different deployment", tc.field) } if !errors.Is(err, workcontext.ErrMintRefused) { t.Errorf("the refusal is %v, want ErrMintRefused", err) @@ -2171,7 +2002,7 @@ func TestEachSealedFieldIsComparedOnItsOwn(t *testing.T) { if err := held.holdSealedIdentity(first); err != nil { t.Fatalf("the first seal was refused: %v", err) } - if err := held.holdSealedIdentity(seal(testInstallation, digest, 11)); err != nil { + if err := held.holdSealedIdentity(seal(testInstallation)); err != nil { t.Errorf("an ordinary renewal to the same execution was refused, which would end every process at its first renewal: %v", err) } }) diff --git a/passthroughtest/passthroughtest.go b/passthroughtest/passthroughtest.go index 2575316..d1e9d71 100644 --- a/passthroughtest/passthroughtest.go +++ b/passthroughtest/passthroughtest.go @@ -242,10 +242,6 @@ func (h *Host) mintWorkload(w http.ResponseWriter, r *http.Request) { OrganizationID: corework.FixtureOrganization, InstallationID: corework.FixtureInstallation, TTL: 10 * time.Minute, - Execution: corework.Execution{ - ImageDigest: corework.FixtureImageDigest, - BuildIncarnation: corework.FixtureBuildIncarnation, - }, }) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]any{"code": 13, "message": err.Error()}) @@ -544,10 +540,6 @@ func capability(seed string) string { OrganizationID: corework.FixtureOrganization, InstallationID: corework.FixtureInstallation, TTL: 10 * time.Minute, - Execution: corework.Execution{ - ImageDigest: corework.FixtureImageDigest, - BuildIncarnation: corework.FixtureBuildIncarnation, - }, }) if err != nil { panic("stand-in capability: " + err.Error()) diff --git a/scoped_credential_test.go b/scoped_credential_test.go index a744f60..5fa8d9a 100644 --- a/scoped_credential_test.go +++ b/scoped_credential_test.go @@ -52,7 +52,7 @@ func TestSharedCredentialsSelectOrganisationAndInstallation(t *testing.T) { TenantID: corework.FixtureTenant, OwnerPrincipalID: corework.FixturePrincipal, OwnerPrincipalKind: "human", TaskID: fmt.Sprintf("scoped-%s-%d", org, n), Audience: testAudience, OrganizationID: org, InstallationID: installations[org], - TTL: time.Minute, Execution: issuers[org].execution(), + TTL: time.Minute, }) if err != nil { t.Errorf("fixture mint: %v", err) diff --git a/solution_test.go b/solution_test.go index 58792c9..05b2ca9 100644 --- a/solution_test.go +++ b/solution_test.go @@ -56,10 +56,6 @@ func capability(seed string) string { OrganizationID: testOrganization, InstallationID: corework.FixtureInstallation, TTL: 10 * time.Minute, - Execution: corework.Execution{ - ImageDigest: corework.FixtureImageDigest, - BuildIncarnation: corework.FixtureBuildIncarnation, - }, }) if err != nil { panic("stand-in capability: " + err.Error()) diff --git a/typed_boundary_test.go b/typed_boundary_test.go index 0ccf283..6036505 100644 --- a/typed_boundary_test.go +++ b/typed_boundary_test.go @@ -1247,7 +1247,7 @@ func fixtureConcreteDecoder(raw []byte, dst *wc.SealedValues) error { // The supported read, which must stay unflagged: a concrete parameter, and no // decoder in sight. -func fixtureSpellingAccessor(seal *wc.SealedValues) string { return seal.GetImageDigest() } +func fixtureSpellingAccessor(seal *wc.SealedValues) string { return seal.GetInstallationId() } ` found := gateFindings(t, fixture, source, reportCapabilityShapes) for _, want := range []string{