| 1 | package hostactors |
| 2 | |
| 3 | import ( |
| 4 | "crypto/sha256" |
| 5 | "encoding/hex" |
| 6 | "encoding/json" |
| 7 | ) |
| 8 | |
| 9 | // BoxBinding is the digest installed claim state on one box binds to: the |
| 10 | // deployment's binding facts and that box's row identity and state, read from |
| 11 | // one verified roster. It changes only when one of those facts changes. Holds, |
| 12 | // activations, enrollment attempts, consoles, regions, other boxes' rows, this |
| 13 | // box's role, label, group, loss ids and enrollment time, and the roster's |
| 14 | // write counter (Revision) never change it, so the console's per-operation |
| 15 | // roster writes no longer strand installed instances. |
| 16 | // |
| 17 | // The second result is false when ref names no row. |
| 18 | func BoxBinding(r Roster, ref string) (string, bool) { |
| 19 | b, ok := r.Boxes[ref] |
| 20 | if !ok { |
| 21 | return "", false |
| 22 | } |
| 23 | sum := sha256.Sum256(boxBindingV1(r, b)) |
| 24 | return hex.EncodeToString(sum[:]), true |
| 25 | } |
| 26 | |
| 27 | // ValidBinding reports whether s has BoxBinding's form: 64 lower-case hex |
| 28 | // digits. It says nothing about which roster or box it was computed from. |
| 29 | func ValidBinding(s string) bool { |
| 30 | if len(s) != 2*sha256.Size { |
| 31 | return false |
| 32 | } |
| 33 | for i := 0; i < len(s); i++ { |
| 34 | if c := s[i]; (c < '0' || c > '9') && (c < 'a' || c > 'f') { |
| 35 | return false |
| 36 | } |
| 37 | } |
| 38 | return true |
| 39 | } |
| 40 | |
| 41 | // boxBindingV1 freezes the binding facts in an ordered v1 tuple, encoded like |
| 42 | // the journal's request tuple (hostbox/journalservice.go). Never extend this |
| 43 | // tuple when Roster or Box grows: a new bound fact requires a new version tag |
| 44 | // and a separate encoder, and every installed stamp changes with it. |
| 45 | func boxBindingV1(r Roster, b Box) []byte { |
| 46 | raw, _ := json.Marshal([]any{ |
| 47 | "carlos-box-binding-v1", |
| 48 | r.DeploymentBucket, r.HomeRegion, r.RequestLimitNS, r.Publication, r.FloorSHA256, |
| 49 | b.Enrollment, b.Namespace, b.BoxID, b.Region, b.Pool, |
| 50 | []any{b.Cloud.Provider, b.Cloud.Account, b.Cloud.Region, b.Cloud.InstanceID}, |
| 51 | b.EvidenceSHA256, b.State, b.QuarantineRevision, hex.EncodeToString(b.CensusPublicKey), |
| 52 | }) |
| 53 | return raw |
| 54 | } |
| 55 | |