package hostguard import ( "context" "errors" ) type MigrationDeny struct { Incarnation string Stamp, Session uint64 } func migrationDenied(denies []MigrationDeny, c Captured, session uint64) bool { for _, d := range denies { if d.Incarnation == c.Incarnation && c.Stamp <= d.Stamp && session <= d.Session { return true } } return false } // RetainMigrationDeny adds only refusal authority. The offline root collector // supplies the independently observed installation; this method grants no work. func (a *RootAuthority) RetainMigrationDeny(ctx context.Context, in RootInstallation, session uint64) error { if a == nil || a.store == nil || validateFileHost(in.Host) != nil || in.Incarnation == "" || in.Stamp == 0 || in.Migration == 0 || in.Account == "" || in.App == "" || in.Record == "" || in.Box == "" || in.Enrollment == "" || session == 0 { return ErrInvalidInput } unlock, err := a.store.Lock(ctx, in.Host+".lock") if err != nil { return err } defer unlock() r, err := a.read(in.Host) if errors.Is(err, ErrNoRecord) { r = record{V: 1, Host: in.Host, Incarnation: in.Incarnation, ActiveStamp: in.Stamp, root: true, installation: in} } else if err != nil { return err } if r.installation.Host != in.Host || r.installation.Incarnation != in.Incarnation || r.installation.Stamp != in.Stamp || r.installation.Account != in.Account || r.installation.App != in.App || r.installation.Record != in.Record || r.installation.Box != in.Box || r.installation.Enrollment != in.Enrollment || r.installation.Migration != in.Migration { return ErrRecordIncarnation } for i, d := range r.migrationDenies { if d.Incarnation == in.Incarnation && d.Stamp == in.Stamp { if session > r.migrationDenies[i].Session { r.migrationDenies[i].Session = session } return a.write(r) } } r.migrationDenies = append(r.migrationDenies, MigrationDeny{Incarnation: in.Incarnation, Stamp: in.Stamp, Session: session}) return a.write(r) } // CompleteMigrationInventory binds the checked inventory only after root has // retained its own completed stop receipt for this denied session. func (a *RootAuthority) CompleteMigrationInventory(ctx context.Context, target FenceTarget, revision uint64, artifacts InstallationArtifacts, previous ...uint64) error { if target.validate(target.Host) != nil || revision == 0 || artifacts.Schema != 1 { return ErrInvalidInput } unlock, err := a.store.Lock(ctx, target.Host+".lock") if err != nil { return err } defer unlock() r, err := a.read(target.Host) if err != nil { return err } captured := Captured{Incarnation: target.Incarnation, Stamp: target.Stamp} if r.Incarnation != target.Incarnation || r.ActiveStamp != target.Stamp || !migrationDenied(r.migrationDenies, captured, target.Session) { return ErrFenceTarget } completed := false for _, receipt := range r.FenceJournal { if receipt.Target == target { completed = true } } prior := revision if len(previous) > 1 || len(previous) == 1 && (previous[0] == 0 || previous[0] == ^uint64(0) || previous[0]+1 != revision) { return ErrInvalidInput } if len(previous) == 1 { prior = previous[0] } if !completed || r.installation.InventoryRevision != 0 && r.installation.InventoryRevision != revision && r.installation.InventoryRevision != prior { return ErrFenceTarget } r.installation.InventoryRevision = revision r.installation.Artifacts = artifacts return a.write(r) } func migrationAdmissionDenied(denies []MigrationDeny, receipts []FenceReceipt, c Captured, session uint64) bool { if migrationDenied(denies, c, session) { return true } for _, d := range denies { if d.Incarnation != c.Incarnation || d.Stamp != c.Stamp { continue } stopped := false for _, r := range receipts { if r.Target.Incarnation == d.Incarnation && r.Target.Stamp == d.Stamp && r.Target.Session == d.Session { stopped = true } } if !stopped { return true } } return false }