// Package hostquarantine is the deployment-wide admission gate for enrolled // boxes. The authoritative directory implementation is deliberately outside // this package; callers supply an exact-object reader and every Check performs // a fresh lookup. package hostquarantine import ( "context" "errors" "fmt" "regexp" "strconv" "strings" "time" ) const ( StateActive = "active" StateQuarantined = "quarantined" StateRetired = "retired" ) var ( ErrAdmission = errors.New("hostquarantine: admission refused") ErrRecord = errors.New("hostquarantine: malformed retirement completion") ) type Actor struct { Enrollment string `json:"enrollment"` State string `json:"state"` QuarantineRevision uint64 `json:"quarantine_revision"` } // ActorDirectory reads one exact actor from the root-signed deployment // directory. Implementations must return an error for missing, unreadable or // unverifiable authority. type ActorDirectory interface { Lookup(context.Context, string) (Actor, error) } // Check admits only the exact active enrollment returned by a fresh lookup. // Missing actors, unreadable authority, identity mismatches, quarantine, // retirement and unknown states all fail closed. func Check(ctx context.Context, dir ActorDirectory, box, enrollment string) error { if dir == nil || box == "" || enrollment == "" { return fmt.Errorf("%w: actor identity or directory is absent", ErrAdmission) } a, err := dir.Lookup(ctx, box) if err != nil { return fmt.Errorf("%w: actor directory lookup: %v", ErrAdmission, err) } if a.Enrollment == "" || a.Enrollment != enrollment { return fmt.Errorf("%w: enrollment mismatch", ErrAdmission) } if a.State != StateActive { return fmt.Errorf("%w: actor state %q", ErrAdmission, a.State) } return nil } type RetirementEntry struct { Host string `json:"host"` Incarnation string `json:"incarnation"` Stamp uint64 `json:"stamp"` RetirementID string `json:"retirement_id"` JournalSHA256 string `json:"journal_sha256"` } // RetirementCompletion is the returning machine's durable retirement // receipt. Readmission also requires the matching fresh private box report; // this object alone does not grant admission. type RetirementCompletion struct { V int `json:"v"` Box string `json:"box"` Enrollment string `json:"enrollment"` InstanceID string `json:"instance_id"` QuarantineRevision uint64 `json:"quarantine_revision"` BootID string `json:"boot_id"` CompletedAt time.Time `json:"completed_at"` Complete bool `json:"complete"` InventorySHA256 string `json:"inventory_sha256"` Entries []RetirementEntry `json:"entries"` } var sha256RE = regexp.MustCompile(`^[0-9a-f]{64}$`) func safeSegment(s string) bool { return s != "" && s != "." && s != ".." && !strings.ContainsAny(s, "/\\") } func RetirementKey(box, enrollment string, revision uint64) (string, error) { if !safeSegment(box) || !safeSegment(enrollment) || revision == 0 { return "", fmt.Errorf("%w: invalid key identity", ErrRecord) } return "control/host-retirement/" + box + "/" + enrollment + "/" + strconv.FormatUint(revision, 10) + ".json", nil } func (r RetirementCompletion) Validate() error { if r.V != 1 || !safeSegment(r.Box) || !safeSegment(r.Enrollment) || r.InstanceID == "" || r.QuarantineRevision == 0 || r.BootID == "" { return fmt.Errorf("%w: invalid header", ErrRecord) } if r.Complete { if r.CompletedAt.IsZero() || !sha256RE.MatchString(r.InventorySHA256) || r.Entries == nil { return fmt.Errorf("%w: completed receipt lacks durable inventory evidence", ErrRecord) } } else if !r.CompletedAt.IsZero() { return fmt.Errorf("%w: incomplete receipt has completed_at", ErrRecord) } for i, e := range r.Entries { if e.Host == "" || e.Incarnation == "" || e.Stamp == 0 || e.RetirementID == "" || !sha256RE.MatchString(e.JournalSHA256) { return fmt.Errorf("%w: invalid entry %d", ErrRecord, i) } } return nil }