// Package boxid is the evidence a platform box presents to be enrolled in // the roster (region HA spec 1 ยง5.1, ruling H11): the box's own AWS-signed // instance identity document, published once under its box reference. The // record grants nothing by itself. Enrollment verifies it (Verifier.Enroll) // and copies the proven account, region and instance id into the roster // entry's cloud field; from then on the roster, never this record, is what // binds a reference to an instance. package boxid import ( "bytes" "context" "encoding/json" "errors" "fmt" "github.com/carlosframework/platform/internal/controlname" "github.com/carlosframework/platform/internal/release" ) const Prefix = "control/boxes/" // ValidBox reports whether ref can be a box reference: one DNS label, so // it can never contain "." or "/" and no two references share a key. func ValidBox(ref string) bool { return controlname.ValidLabel(ref) } // Key is where a box's record lives. Callers check ValidBox first. func Key(box string) string { return Prefix + box + ".json" } type Record struct { V int `json:"v"` Box string `json:"box"` IdentityDocument string `json:"identity_document"` IdentitySignature string `json:"identity_signature"` WrittenAt string `json:"written_at"` } // Publish writes a box's record create-only. Writing the same bytes again // succeeds; a different record for the same box is ErrCASConflict, because // a record is never replaced. func Publish(ctx context.Context, s release.Store, rec Record) error { if !ValidBox(rec.Box) { return fmt.Errorf("boxid: %q is not a box reference", rec.Box) } body, err := json.Marshal(rec) if err != nil { return fmt.Errorf("boxid: marshal: %w", err) } _, err = s.Put(ctx, Key(rec.Box), body, "") if !errors.Is(err, release.ErrCASConflict) { return err } cur, _, gerr := s.Get(ctx, Key(rec.Box)) if gerr != nil { return fmt.Errorf("boxid: %s exists and cannot be read: %w", rec.Box, gerr) } if bytes.Equal(cur, body) { return nil } return fmt.Errorf("boxid: %s already has a different record: %w", rec.Box, release.ErrCASConflict) } // Load returns the exact bytes of a box's record, which is what enrollment // verifies and hashes. A missing record is os.ErrNotExist-wrapped. func Load(ctx context.Context, s release.Store, box string) ([]byte, error) { if !ValidBox(box) { return nil, fmt.Errorf("%w: %q is not a box reference", ErrUnverified, box) } raw, _, err := s.Get(ctx, Key(box)) return raw, err }