package host import ( "bytes" "context" "crypto/ed25519" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostbox" ) // This projection can bootstrap root's cleanup service after quarantine. Its // actor explicitly lacks execution eligibility; it must never replace Identity // in a work selector. Root's exact claim/fence, journal and peer checks still // authorize each stop, retirement and settlement independently. type claimCleanupEnrollment struct { actor hostbox.Actor directory hostactors.Directory } type cleanupCloudRoster struct{ actor hostbox.Actor } func (p cleanupCloudRoster) Actor(context.Context, string) (hostbox.Actor, error) { a := p.actor // IdentityProvider verifies cloud evidence against this immutable row. These // values exist only in this private verification copy and are cleared below. a.Eligible, a.Quarantined = true, false return a, nil } func verifyClaimCleanupEnrollment(ctx context.Context, identity hostbox.IdentityProvider, directory hostactors.Directory) (*claimCleanupEnrollment, error) { roster, err := directory.Read(ctx) if err != nil { return nil, err } row, ok := roster.Roster.Boxes[identity.Box] if !ok || roster.Roster.RequestLimitNS != int64(hostbox.RequestLimit) { return nil, ErrClaimStopIdentity } // Binding is the row's binding in this startup read. It stamps only what the // cleanup identity itself constructs; fresh authority is never this actor. binding, _ := hostactors.BoxBinding(roster.Roster, identity.Box) actor := hostbox.Actor{FleetHomeRegion: roster.Roster.HomeRegion, ControlBucket: directory.Bucket, Box: row.BoxID, Enrollment: row.Enrollment, Revision: roster.Roster.Revision, Binding: binding, Region: row.Region, Bucket: identity.Bucket, Namespace: row.Namespace, Cloud: hostbox.CloudIdentity{Provider: row.Cloud.Provider, Account: row.Cloud.Account, Region: row.Cloud.Region, InstanceID: row.Cloud.InstanceID}} identity.Roster = cleanupCloudRoster{actor} if _, err := identity.Verify(ctx); err != nil { return nil, err } actor.HomeRegion = identity.HomeRegion actor.Eligible, actor.Quarantined = false, true return &claimCleanupEnrollment{actor: actor, directory: directory}, nil } func (p *claimCleanupEnrollment) configureSigner(ctx context.Context, c *ClaimStopCoordinator, private ed25519.PrivateKey) error { if p == nil || c == nil || c.Now == nil || len(private) != ed25519.PrivateKeySize { return ErrClaimStopUnavailable } roster, err := p.directory.Read(ctx) if err != nil { return err } row, ok := roster.Roster.Boxes[p.actor.Box] if !ok || row.Enrollment != p.actor.Enrollment || roster.Roster.RequestLimitNS != int64(hostbox.RequestLimit) || !bytes.Equal(row.CensusPublicKey, private.Public().(ed25519.PublicKey)) { return ErrClaimStopIdentity } c.EvidenceSigner = append(ed25519.PrivateKey(nil), private...) c.RequestLimit = hostbox.RequestLimit return nil } // rosterRevision is root's own verified roster read at the moment of the // call (directory.Read checks the deployment's signature): the write // counter of a roster that still names this box under its enrollment. It // holds while the box is quarantined, so historical cleanup keeps working; // only the bound it gives a stop request's numeral matters (fix round 1, // finding 3). It is never the startup read. func (p *claimCleanupEnrollment) rosterRevision(ctx context.Context) (uint64, error) { if p == nil { return 0, ErrClaimStopUnavailable } roster, err := p.directory.Read(ctx) if err != nil { return 0, err } row, ok := roster.Roster.Boxes[p.actor.Box] if !ok || row.Enrollment != p.actor.Enrollment || roster.Roster.Revision == 0 { return 0, ErrClaimStopIdentity } return roster.Roster.Revision, nil } func (o ClaimOwnerBindings) actorForConstruction(ctx context.Context, mode string) (hostbox.Actor, error) { if mode == "instance-control" && o.cleanupEnrollment != nil { return o.cleanupEnrollment.actor, nil } return o.Identity.Verify(ctx) } func (a ClaimAssembly) actorForRoot(ctx context.Context) (hostbox.Actor, error) { if a.Mode == "instance-control" && a.cleanupEnrollment != nil { return a.cleanupEnrollment.actor, nil } return a.Identity.Verify(ctx) }