Bound guard helpers and preserve unmanaged legacy routes
3 files changed,
+60
−4
internal/maintenanceguard/guard.go+4 −3internal/maintenanceguard/types.go+10 −1internal/maintenanceguard/wire_test.go+46 −0
diff --git a/internal/maintenanceguard/guard.go b/internal/maintenanceguard/guard.go| index 1739addfe..0db0bc804 100644 |
| --- a/internal/maintenanceguard/guard.go |
| +++ b/internal/maintenanceguard/guard.go |
| @@ -147,9 +147,10 @@ const ( |
| // CheckServing is a denial check, not a lease or process-health permit. The |
| // caller supplies contracts from an already verified signed release manifest, |
| // verifies the current record identity, and repeats this check after lease |
| -// acquisition before starting or seeding anything. |
| +// acquisition before starting or seeding anything. Legacy unmanaged records may |
| +// omit Record: both barriers must be absent before that omission is accepted. |
| func (r *Repository) CheckServing(ctx context.Context, identity Identity, projection *Projection, verifiedContracts []string) (ServingDecision, error) { |
| - if !identity.valid() || !ValidContracts(verifiedContracts) { |
| + if !identity.locationValid() || !ValidContracts(verifiedContracts) { |
| return Denied, ErrDenied |
| } |
| s, e := r.ReadGuard(ctx, identity.Host) |
| @@ -162,7 +163,7 @@ func (r *Repository) CheckServing(ctx context.Context, identity Identity, projec |
| } |
| return Denied, ErrDenied |
| } |
| - if projection == nil || !projection.valid() || s.value.Identity != identity || s.value.Phase != Resumed || !containsAll(verifiedContracts, s.value.RequiredContracts) { |
| + if !identity.valid() || projection == nil || !projection.valid() || s.value.Identity != identity || s.value.Phase != Resumed || !containsAll(verifiedContracts, s.value.RequiredContracts) { |
| return Denied, ErrDenied |
| } |
| expected, e := s.Projection() |
diff --git a/internal/maintenanceguard/types.go b/internal/maintenanceguard/types.go| index afe6e8fad..a8d6911fb 100644 |
| --- a/internal/maintenanceguard/types.go |
| +++ b/internal/maintenanceguard/types.go |
| @@ -30,10 +30,16 @@ var ( |
| ) |
| func ValidID(s string) bool { |
| + if len(s) != 43 { |
| + return false |
| + } |
| b, e := base64.RawURLEncoding.Strict().DecodeString(s) |
| return e == nil && len(b) == 32 && base64.RawURLEncoding.EncodeToString(b) == s |
| } |
| func ValidDigest(s string) bool { |
| + if len(s) != 64 { |
| + return false |
| + } |
| b, e := hex.DecodeString(s) |
| return e == nil && len(b) == 32 && hex.EncodeToString(b) == s |
| } |
| @@ -89,7 +95,10 @@ type Identity struct { |
| } |
| func (i Identity) valid() bool { |
| - return controlname.ValidAccount(i.Account) && controlname.ValidApp(i.App) && controlname.ValidHostShape(i.Host) && ValidDigest(i.Record) |
| + return i.locationValid() && ValidDigest(i.Record) |
| +} |
| +func (i Identity) locationValid() bool { |
| + return controlname.ValidAccount(i.Account) && controlname.ValidApp(i.App) && controlname.ValidHostShape(i.Host) |
| } |
| type Reference struct { |
diff --git a/internal/maintenanceguard/wire_test.go b/internal/maintenanceguard/wire_test.go| index dabb770fa..ca5f1a3ae 100644 |
| --- a/internal/maintenanceguard/wire_test.go |
| +++ b/internal/maintenanceguard/wire_test.go |
| @@ -234,3 +234,49 @@ func TestPermanentReferenceDigestMismatchDenies(t *testing.T) { |
| replace(t, f, key, []byte(`{"metadata":"changed outside immutable writer"}`)) |
| denied(t, f, &p, []string{"home.operator.v1"}) |
| } |
| + |
| +func TestDirectHelpersBoundBeforeAllocation(t *testing.T) { |
| + raw := strings.Repeat("a", 1<<20) |
| + for name, fn := range map[string]func(string) bool{"id": guard.ValidID, "digest": guard.ValidDigest} { |
| + t.Run(name, func(t *testing.T) { |
| + if allocations := testing.AllocsPerRun(10, func() { |
| + if fn(raw) { |
| + panic("oversized helper input accepted") |
| + } |
| + }); allocations != 0 { |
| + t.Fatalf("oversized direct input allocated before refusal: %v", allocations) |
| + } |
| + }) |
| + } |
| +} |
| +func TestLegacyUnmanagedIdentityNeedsNoCreationDigest(t *testing.T) { |
| + f := setup(t) |
| + legacy := f.identity |
| + legacy.Record = "" |
| + if d, e := f.repo.CheckServing(ctx, legacy, nil, nil); e != nil || d != guard.Unmanaged { |
| + t.Fatalf("legacy route: %v %v", d, e) |
| + } |
| + bogus := legacy |
| + bogus.Host = "../foreign" |
| + if d, e := f.repo.CheckServing(ctx, bogus, nil, nil); e == nil || d != guard.Denied { |
| + t.Fatal("invalid location accepted") |
| + } |
| + p := guard.Projection{Version: 1, Generation: 1, GuardDigest: hash("absent")} |
| + if d, e := f.repo.CheckServing(ctx, legacy, &p, nil); e == nil || d != guard.Denied { |
| + t.Fatal("projection alone accepted") |
| + } |
| + c := f.claim(t, 1) |
| + s := f.create(t, c) |
| + for _, phase := range []guard.Phase{guard.Quiescing, guard.Fenced, guard.ResumeRequested, guard.Resumed} { |
| + s = f.step(t, c, s, phase) |
| + } |
| + p, e := s.Projection() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + for _, projection := range []*guard.Projection{nil, &p} { |
| + if d, e := f.repo.CheckServing(ctx, legacy, projection, []string{"home.operator.v1"}); e == nil || d != guard.Denied { |
| + t.Fatal("managed record lacking identity accepted") |
| + } |
| + } |
| +} |