package hostops import ( "context" "crypto/ed25519" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "os" "sort" "sync" "time" "github.com/carlosframework/platform/internal/domains" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" ) const BackfillKey = "control/hosts-backfill.json" const HostsQuarantineKey = "control/hosts-quarantine.json" type CensusInstallation struct { Placement string `json:"placement"` Host string `json:"host"` Account string `json:"account"` App string `json:"app"` Family string `json:"family"` Key string `json:"key"` Incarnation string `json:"incarnation"` Stamp uint64 `json:"stamp"` Local bool `json:"local"` Route bool `json:"route"` } type CensusReport struct { Box string `json:"box"` Enrollment string `json:"enrollment"` BootID string `json:"boot_id"` Reservation string `json:"reservation"` DirectoryRevision uint64 `json:"directory_revision"` CapturedAt time.Time `json:"captured_at"` Complete bool `json:"complete"` Installations []CensusInstallation `json:"installations"` // Seeds is the box's own seed state (backfill_seeds.go). Signed with // the rest of the report. Nil is a box that did not say, which // activation reads as unknown and refuses. Seeds *CensusSeeds `json:"seeds,omitempty"` } type Census struct { Records []Record Reports map[string]CensusReport Roster hostactors.Observed } type BackfillHost struct { Key string `json:"key"` ETag string `json:"etag"` Acquisition string `json:"acq"` Region string `json:"region"` Box string `json:"box"` } type BackfillPlan struct { V int `json:"v"` ID string `json:"id"` Phase string `json:"phase"` DirectoryRevision uint64 `json:"directory_revision"` CreatedAt time.Time `json:"created_at"` Reports map[string]string `json:"reports"` Hosts map[string]BackfillHost `json:"hosts"` } type HostsQuarantine struct { V int `json:"v"` Hosts map[string]string `json:"hosts"` } type CensusEnvelope struct { Report CensusReport `json:"report"` Signature []byte `json:"signature"` } type Backfill struct { mu sync.Mutex cached *Census cachedTag string Store release.Store Directory hostactors.Directory } type ProductionAuthority struct { HomeRegion string Store release.Store Control *ControlWriter Directory hostactors.Directory Now func() time.Time ConsoleID, Enrollment, Namespace string Signer release.Signer Evidence hostactors.Evidence Backfill *Backfill // Executors is create's executor selection (region HA ยง5.2, L8). The // create door sets it only with CARLOS_REGION_HA on; nil keeps today's // create path (claim Plan 5's selectExecutor), so a claim-enabled // deployment with HA off (every one-box region included) creates // exactly as before. Executors *ExecutorChooser } // SelectExecutor is Authority's create-time selection: choose() once, // before the create CAS, when a chooser is configured; otherwise no // selection, and the create is today's. The chooser chooses within claim // Plan 5's scope: this authority's namespace and the record's pool. // // A door that already chose for this host (WithChosenExecutor) is held to // that choice, so the create names the box the door checked (H27). func (a ProductionAuthority) SelectExecutor(ctx context.Context, obs hostclaim.Observed, r Record) (hostclaim.ExecutorSelection, bool, error) { if a.Executors == nil { return hostclaim.ExecutorSelection{}, false, nil } if sel, ok := ChosenExecutor(ctx, r.Holder.Host); ok { return sel, true, nil } var shape struct { Pool string `json:"pool"` } if err := json.Unmarshal(r.Body, &shape); err != nil { return hostclaim.ExecutorSelection{}, false, err } x := *a.Executors x.Namespace, x.Pool = a.Namespace, shape.Pool sel, err := x.Choose(ctx, obs, r) return sel, err == nil, err } func reportKey(reservation, box string) (string, error) { if !safeSegment(reservation) || !safeSegment(box) { return "", ErrIdentity } return "control/host-census/" + reservation + "/" + box + ".json", nil } func safeSegment(s string) bool { if s == "" || s == "." || s == ".." { return false } for _, c := range s { if c == '/' || c == '\\' || c == 0 { return false } } return true } func CheckHostQuarantine(ctx context.Context, s release.Store, host string) error { raw, _, err := s.Get(ctx, HostsQuarantineKey) if errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return err } var q HostsQuarantine if decode(raw, &q) != nil || q.V != 1 || q.Hosts == nil { return ErrIdentity } if _, ok := q.Hosts[host]; ok { return ErrPending } return nil } func (b *Backfill) read(ctx context.Context) (BackfillPlan, string, error) { p, tag, err := b.readPlan(ctx) if err != nil { return p, "", err } if p.Phase != "adopting" { return p, "", ErrContract } return p, tag, nil } func (b *Backfill) readPlan(ctx context.Context) (BackfillPlan, string, error) { var p BackfillPlan raw, tag, err := b.Store.Get(ctx, BackfillKey) if err != nil { return p, "", err } if tag == "" || release.VerifyRootEnvelope(b.Directory.PublicKey, raw, &p) != nil || p.V != 1 || !safeSegment(p.ID) || p.CreatedAt.IsZero() || (p.Phase == "adopting" && (p.Hosts == nil || p.Reports == nil)) { return p, "", ErrContract } return p, tag, nil } func (a ProductionAuthority) census(ctx context.Context, account, app, reservation string, expectedRevision uint64, digests map[string]string) (Census, error) { out := Census{Reports: map[string]CensusReport{}} roster, err := a.Directory.Read(ctx) if err != nil { return out, err } if expectedRevision != 0 && roster.Roster.Revision != expectedRevision { return out, ErrPending } out.Roster = roster apps, err := Apps(ctx, a.Store) if err != nil { return out, err } allRecords := map[string]Record{} for _, p := range apps { records, err := Records(ctx, a.Store, p.Account, p.Name) if err != nil { return out, err } for _, r := range records { if _, ok := allRecords[r.Holder.Host]; ok { return out, ErrIdentity } allRecords[r.Holder.Host] = r if account == "" || (p.Account == account && p.Name == app) { out.Records = append(out.Records, r) } } } for ref, box := range roster.Roster.Boxes { // Retired membership is retained until its inventory is proven empty. key, err := reportKey(reservation, ref) if err != nil { return out, err } raw, tag, err := a.Store.Get(ctx, key) if err != nil { return out, err } if digests != nil { sum := sha256.Sum256(raw) if digests[ref] != hex.EncodeToString(sum[:]) { return out, ErrIdentity } } var envelope CensusEnvelope if decode(raw, &envelope) != nil { return out, ErrIdentity } report := envelope.Report canonical, e := json.Marshal(report) if e != nil || len(box.CensusPublicKey) != ed25519.PublicKeySize || !ed25519.Verify(box.CensusPublicKey, canonical, envelope.Signature) { return out, ErrIdentity } if tag == "" || !report.Complete || report.Box != ref || report.Enrollment != box.Enrollment || report.Reservation != reservation || report.DirectoryRevision != roster.Roster.Revision || report.BootID == "" || report.CapturedAt.IsZero() || report.Installations == nil { return out, ErrIdentity } seen := map[string]bool{} for _, i := range report.Installations { if i.Host == "" || seen[i.Host] || (!i.Route && !i.Local) { return out, ErrIdentity } seen[i.Host] = true r, ok := allRecords[i.Host] if !ok || i.Account != r.Holder.Account || i.App != r.Holder.App || i.Key != r.Holder.Key || i.Family != r.Holder.Family { return out, ErrIdentity } inc, err := r.Incarnation() if err != nil || (i.Incarnation != "" && i.Incarnation != inc) { return out, ErrIdentity } } out.Reports[ref] = report } if digests != nil && len(digests) != len(out.Reports) { return out, ErrIdentity } fresh, err := a.Directory.Read(ctx) if err != nil { return out, err } if fresh.ETag != roster.ETag { return out, ErrPending } return out, nil } func (a ProductionAuthority) Census(ctx context.Context, account, app, reservation string) (Census, error) { return a.census(ctx, account, app, reservation, 0, nil) } func (a ProductionAuthority) Inventory(ctx context.Context, obs hostclaim.Observed) ([]string, []string, error) { roster, err := a.Directory.Read(ctx) if err != nil { return nil, nil, err } if len(roster.Roster.Regions) == 0 { return nil, nil, ErrContract } regions := make([]string, 0, len(roster.Roster.Regions)) boxes := make([]string, 0, len(roster.Roster.Boxes)) for r := range roster.Roster.Regions { regions = append(regions, r) } for b := range roster.Roster.Boxes { boxes = append(boxes, b) } sort.Strings(regions) sort.Strings(boxes) return regions, boxes, nil } func (a ProductionAuthority) Adoption(ctx context.Context, r Record) (region, box string, regions, boxes []string, err error) { if a.Backfill == nil { err = ErrContract return } return a.Backfill.Adoption(ctx, r) } func (b *Backfill) Adoption(ctx context.Context, r Record) (region, box string, regions, boxes []string, err error) { if err = CheckHostQuarantine(ctx, b.Store, r.Holder.Host); err != nil { return } p, tag, e := b.read(ctx) if e != nil { err = e return } selected, ok := p.Hosts[r.Holder.Host] if !ok || selected.Key != r.Holder.Key || selected.ETag != r.ETag || selected.Acquisition == "" || selected.Region == "" || selected.Box == "" { err = ErrIdentity return } a := ProductionAuthority{Store: b.Store, Directory: b.Directory} b.mu.Lock() if b.cachedTag != tag { b.cached = nil } var census Census if b.cached != nil { census = *b.cached } else { census, e = a.census(ctx, "", "", p.ID, p.DirectoryRevision, p.Reports) if e == nil { b.cached = &census b.cachedTag = tag } } b.mu.Unlock() if e != nil { err = e return } current, e := a.Directory.Read(ctx) if e != nil { err = e return } if current.ETag != census.Roster.ETag { err = ErrPending return } owners := map[string]bool{} found := false for _, rec := range census.Records { if rec.Holder.Host == r.Holder.Host { found = rec.ETag == r.ETag && rec.Holder.Key == r.Holder.Key } } for ref, report := range census.Reports { for _, i := range report.Installations { if i.Host == r.Holder.Host { owners[ref] = true } } } if !found || len(owners) != 1 || !owners[selected.Box] { err = ErrIdentity return } enrolled, ok := census.Roster.Roster.Boxes[selected.Box] if !ok || enrolled.Region != selected.Region || enrolled.State != "active" { err = ErrIdentity return } if r.Holder.Family == "instance" { var instance provision.Record if json.Unmarshal(r.Body, &instance) != nil || len(instance.Replicas) != 0 || provision.ClaimEverywhere(instance) { err = ErrIdentity return } } raw, _, e := b.Store.Get(ctx, domains.ClaimKey(r.Holder.Host)) if e != nil { err = e return } var ownership domains.Claim inc, e := r.Incarnation() if e != nil { err = e return } if decode(raw, &ownership) != nil || ownership.Host != r.Holder.Host || ownership.Account != r.Holder.Account || ownership.App != r.Holder.App || ownership.Acquisition != selected.Acquisition || ownership.RecordIncarnation != inc || ownership.OwnershipState != "committed" || ownership.Pending { err = ErrIdentity return } if r.Holder.Acquisition != selected.Acquisition { err = ErrIdentity return } _, freshTag, e := b.read(ctx) if e != nil { err = e return } if freshTag != tag { err = ErrPending return } for region := range census.Roster.Roster.Regions { if region != selected.Region { regions = append(regions, region) } } for ref := range census.Roster.Roster.Boxes { if ref != selected.Box { boxes = append(boxes, ref) } } sort.Strings(regions) sort.Strings(boxes) return selected.Region, selected.Box, regions, boxes, nil }