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.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/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/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/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.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/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.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)) 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{