package host import ( "context" "path/filepath" "reflect" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" ) func (h ClaimRootHTTP) servingHandover() hostbox.ServingHandover { return hostbox.ServingHandover{ColdHandover: hostbox.ColdHandover{Guard: h.Stop.Coordinator.Guard, Claims: h.Stop.Coordinator.Evidence, Registry: h.Reconcile.Registry, Inventory: h.Reconcile.Inventory, Launches: h.Launches, Roots: h.Roots, JournalDir: filepath.Join(h.Reconcile.Retirement.JournalDir, "serving"), VerifyReceipt: verifyRetirementReceipt}, Validate: h.validateServingHandover, VerifyDirectory: h.verifyServingHandoverDirectory} } func (h ClaimRootHTTP) validateServingHandover(ctx context.Context, c *hostclaim.Claim, prior hostguard.FenceTarget, next hostbox.ColdSuccessor, in hostbox.StopInventory, installation hostguard.RootInstallation) error { if h.RequireNewWork == nil || h.InventoryActor == nil { return hostbox.ErrUnsupported } if err := h.RequireNewWork(ctx); err != nil { return err } actor, err := h.InventoryActor(ctx) if err != nil { return err } if !actor.Eligible || actor.Quarantined || actor.Revision == 0 || actor.Box != prior.Box || actor.Region != prior.Region || actor.Enrollment != prior.Enrollment || !servingPriorBinding(next.DirectoryRevision, actor) { return hostbox.ErrChanged } if !servingPriorBinding(prior.DirectoryRevision, actor) { return hostbox.ErrChanged } current, err := servingWriterIdentity(hostclaim.Observed{Host: prior.Host, Claim: c}, actor.Box, actor.Region) if err != nil { return err } holder := c.Holder if installation.Empty || installation.Host != prior.Host || installation.Incarnation != prior.Incarnation || installation.Stamp != prior.Stamp || installation.Migration == 0 || installation.Account != holder.Account || installation.App != holder.App || installation.Record != holder.Key || installation.Box != actor.Box || installation.Enrollment != actor.Enrollment { return hostbox.ErrChanged } canonical, _, err := rootClaimRecord(ctx, h.Stop.Coordinator.Evidence, c, h.Roots["data"]) if err != nil { return err } canonical.ClaimIncarnation, canonical.ClaimStamp = prior.Incarnation, prior.Stamp for _, e := range in.Entries { if e.Kind == "route" { canonical.ClaimOwner, canonical.ClaimRowID = e.Ownership, e.RowIdentity } } if err = hostbox.ValidateAgentInventory(in, &canonical, h.Roots["data"]); err != nil { return err } var admission hostclaim.AdmissionRecord for _, a := range holder.Admissions { if a.Session == current.Session { admission = a } } if prior.Session != 0 { p, err := servingPredecessor(c, current, prior.Stamp, admission.Predecessor) if err != nil || p.Session != prior.Session || admission.Predecessor != p.Generation || admission.PredecessorRegion != p.Region || admission.WritesLost == nil || admission.WritesLost.Box != p.Box || admission.WritesLost.Session != p.Session || admission.WritesLost.Generation != p.Generation { return hostbox.ErrChanged } for _, a := range holder.Admissions { if a.Session > p.Session && a.Session < current.Session { return hostbox.ErrChanged } } } else { if h.servingParks == nil || h.servingParks.state == nil { return hostbox.ErrUnsupported } body, err := h.servingParks.state.Read(servingLegacyKey("legacy-stop", current)) if err != nil { return err } var saved servingLegacyStop if servingParkDecode(body, &saved) != nil || validateServingLegacyStop(saved) != nil || saved.Result.Target != prior { return hostbox.ErrChanged } old := saved.Observed.Claim.Holder if old.Account != holder.Account || old.App != holder.App || old.Key != holder.Key || old.Incarnation != holder.Incarnation || !old.CreatedAt.Equal(holder.CreatedAt) || admission.Predecessor != old.Generation { return hostbox.ErrChanged } for _, a := range holder.Admissions { if a.Session < current.Session { return hostbox.ErrChanged } } if old.WriterBox == "" { if admission.WritesLost != nil || admission.PredecessorRegion != "" { return hostbox.ErrChanged } } else if old.WriterBox != actor.Box || admission.WritesLost == nil || admission.WritesLost.Box != actor.Box || admission.WritesLost.Session != 0 || admission.WritesLost.Generation != "" || admission.PredecessorRegion != actor.Region { return hostbox.ErrChanged } } fresh, err := hostclaim.Read(ctx, h.Stop.Coordinator.Evidence, prior.Host) if err != nil { return err } if fresh.Absent || !reflect.DeepEqual(fresh.Claim, c) { return hostbox.ErrChanged } return nil } // Session zero sends a selector. Root supplies the complete retained stop tuple. func (h ClaimRootHTTP) servingLegacyHandoverTarget(ctx context.Context, q coldHandoverRequest) (hostguard.FenceTarget, error) { if h.servingParks == nil || h.servingParks.state == nil { return hostguard.FenceTarget{}, hostbox.ErrUnsupported } p, n := q.Prior, q.Next selector := hostguard.FenceTarget{Host: n.Host, Incarnation: n.Incarnation, Stamp: p.Stamp, Box: n.Box, Enrollment: n.Enrollment} if p != selector || n.Op != "" || n.Phase != "" { return hostguard.FenceTarget{}, hostbox.ErrChanged } body, err := h.servingParks.state.Read(servingLegacyKey("legacy-stop", servingArtifactIdentity{Host: n.Host, Box: n.Box, Incarnation: n.Incarnation})) if err != nil { return hostguard.FenceTarget{}, err } var saved servingLegacyStop if servingParkDecode(body, &saved) != nil || validateServingLegacyStop(saved) != nil { return hostguard.FenceTarget{}, hostbox.ErrChanged } t := saved.Result.Target if t.Host != p.Host || t.Incarnation != p.Incarnation || t.Stamp != p.Stamp || t.Box != p.Box || t.Enrollment != p.Enrollment { return hostguard.FenceTarget{}, hostbox.ErrChanged } return t, ctx.Err() } // servingPriorBinding reports whether a recorded installation stamp is the // fresh actor's binding. It is equality, not an ordering: a binding is a // digest, and an installation stamped under any other binding (before a // quarantine and readmission, say) is never served again. func servingPriorBinding(recorded string, actor hostbox.Actor) bool { return actor.Binding != "" && recorded == actor.Binding }