package hostactors import ( "context" "github.com/carlosframework/platform/internal/boxid" "github.com/carlosframework/platform/internal/release" ) type EnrollmentRequest struct { AttemptID string ConfirmationID string Reference string Namespace string Label string Region string Pool string AWSRegion string TofuInstanceID string ExistingReference bool Operator string } // Enroll uses operator-approved configuration, not values in the identity // request. H16/H17's worker confirmation producer belongs to region HA spec 4. func (d Directory) Enroll(ctx context.Context, sg release.Signer, o Observed, v boxid.Verifier, in EnrollmentRequest, raw []byte) (Observed, error) { return d.enroll(ctx, sg, o, v, in, raw, false) } func (d Directory) enroll(ctx context.Context, sg release.Signer, o Observed, v boxid.Verifier, in EnrollmentRequest, raw []byte, automatic bool) (Observed, error) { if (!automatic && in.Operator == "") || in.AWSRegion == "" || in.Reference == "" { return Observed{}, ErrRoster } referenced, err := d.Referenced(ctx, in.Reference) if err != nil { return Observed{}, err } in.ExistingReference = in.ExistingReference || referenced if automatic && referenced { return Observed{}, ErrRoster } if in.ExistingReference && in.TofuInstanceID == "" { return Observed{}, ErrRoster } expect := boxid.Expect{Box: in.Reference, AWSRegion: in.AWSRegion, TofuInstanceID: in.TofuInstanceID} for ref, b := range o.Roster.Boxes { expect.Roster = append(expect.Roster, boxid.Enrolled{Box: ref, Cloud: b.Cloud}) } evidence, err := v.Enroll(raw, expect) if err != nil { return Observed{}, err } cloud := evidence.Cloud if old, exists := o.Roster.Boxes[in.Reference]; exists { if old.Cloud == cloud && old.EvidenceSHA256 == evidence.RecordSHA256 && old.Namespace == in.Namespace && old.Region == in.Region && old.Pool == in.Pool { return o, nil } return Observed{}, ErrRoster } enrollment, err := d.enrollmentNonce(ctx, in, raw) if err != nil { return Observed{}, err } r := Clone(o.Roster) if automatic { a, ok := r.EnrollmentAttempts[in.Reference] if !ok || a.Consumed || a.AttemptID != in.AttemptID || a.ConfirmationID != in.ConfirmationID { return Observed{}, ErrRoster } a.Consumed = true r.EnrollmentAttempts[in.Reference] = a } box := Box{Enrollment: enrollment, Namespace: in.Namespace, BoxID: in.Reference, Label: in.Label, Region: in.Region, Pool: in.Pool, Cloud: cloud, EvidenceSHA256: evidence.RecordSHA256, State: "active", LossIDs: []string{}} if automatic { // A box the group launched takes the group's role in this same // write (region HA spec 2 ยง7, A11-1): a replacement for an // excluded group is never placeable, even for a moment. g := r.Regions[in.Region] box.Group, box.Role = g.GroupName, g.Role } r.Boxes[in.Reference] = box return d.Replace(ctx, sg, o, r) } func (d Directory) EnrollAWS(ctx context.Context, sg release.Signer, o Observed, in EnrollmentRequest, raw []byte) (Observed, error) { region, ok := o.Roster.Regions[in.Region] if !ok || region.Account == "" || region.AWSRegion == "" { return Observed{}, ErrRoster } if err := validateScope(o.Roster, Scope{Namespace: in.Namespace, Regions: []string{in.Region}, Pools: []string{in.Pool}}); err != nil { return Observed{}, err } certs, err := boxid.EmbeddedCerts() if err != nil { return Observed{}, err } in.AWSRegion = region.AWSRegion return d.Enroll(ctx, sg, o, boxid.Verifier{Account: region.Account, Certs: certs}, in, raw) }