carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "bytes"
5 "crypto/ed25519"
6 "encoding/base64"
7 "encoding/json"
8 "errors"
9 "net/url"
10
11 "github.com/carlosframework/platform/internal/controlname"
12 "github.com/carlosframework/platform/internal/maintenanceguard"
13)
14
15const MaxWorkerControlBytes = 32 << 10
16
17var ErrWorkerControl = errors.New("worker control record refused")
18
19type WorkerHead struct {
20 Schema int `json:"schema"`
21 Kind string `json:"kind"`
22 Deployment string `json:"deployment"`
23 Account string `json:"account"`
24 Box string `json:"box"`
25 PhysicalIdentity string `json:"physical_identity"`
26 Generation int64 `json:"generation"`
27 Revision int64 `json:"revision"`
28 TokenHash string `json:"token_hash"`
29 Status string `json:"status"`
30 Placement string `json:"placement"`
31 Candidate maintenanceguard.Reference `json:"candidate"`
32 Open []maintenanceguard.Reference `json:"open"`
33}
34
35type WorkerAssignmentIdentity struct {
36 Assignment maintenanceguard.Reference `json:"assignment"`
37 Deployment string `json:"deployment"`
38 Box string `json:"box"`
39 Generation int64 `json:"generation"`
40 Account string `json:"account"`
41 App string `json:"app"`
42 Host string `json:"host"`
43 Operation string `json:"operation"`
44 Request string `json:"request"`
45 Attempt string `json:"attempt"`
46}
47
48type WorkerAssignmentState struct {
49 Schema int `json:"schema"`
50 Kind string `json:"kind"`
51 Identity WorkerAssignmentIdentity `json:"identity"`
52 Revision int64 `json:"revision"`
53 Disabled bool `json:"disabled"`
54 Expires int64 `json:"expires_at"`
55 Preflight *maintenanceguard.Reference `json:"preflight,omitempty"`
56 Compatibility *maintenanceguard.Reference `json:"compatibility,omitempty"`
57 Execution *maintenanceguard.Reference `json:"execution,omitempty"`
58 Resume *maintenanceguard.Reference `json:"resume,omitempty"`
59}
60
61func workerOrigin(s string) bool {
62 u, e := url.Parse(s)
63 return e == nil && u.Scheme == "https" && u.User == nil && controlname.ValidHostShape(u.Host) && s == "https://"+u.Host
64}
65func workerRef(r maintenanceguard.Reference) bool {
66 return maintenanceguard.ValidID(r.ID) && maintenanceguard.ValidDigest(r.Digest)
67}
68func (h WorkerHead) Valid() bool {
69 if h.Schema != 1 || h.Kind != "maintenance-worker" || !workerOrigin(h.Deployment) || !controlname.ValidAccount(h.Account) || !controlname.ValidApp(h.Box) || !maintenanceguard.ValidDigest(h.PhysicalIdentity) || h.Generation <= 0 || h.Revision <= 0 || !maintenanceguard.ValidDigest(h.TokenHash) || (h.Status != "active" && h.Status != "revoking" && h.Status != "detached") || (h.Placement != "" && !controlname.ValidApp(h.Placement)) || !workerRef(h.Candidate) || h.Open == nil || len(h.Open) > 8 || (h.Status == "detached" && len(h.Open) != 0) {
70 return false
71 }
72 seen := map[string]bool{}
73 for _, ref := range h.Open {
74 if !workerRef(ref) || seen[ref.ID] {
75 return false
76 }
77 seen[ref.ID] = true
78 }
79 return true
80}
81func (i WorkerAssignmentIdentity) Valid() bool {
82 return workerRef(i.Assignment) && workerOrigin(i.Deployment) && controlname.ValidApp(i.Box) && i.Generation > 0 && controlname.ValidAccount(i.Account) && controlname.ValidApp(i.App) && controlname.ValidHostShape(i.Host) && maintenanceguard.ValidID(i.Operation) && maintenanceguard.ValidID(i.Request) && maintenanceguard.ValidID(i.Attempt)
83}
84func (s WorkerAssignmentState) Valid() bool {
85 if s.Schema != 1 || s.Kind != "maintenance-assignment" || !s.Identity.Valid() || s.Revision <= 0 || s.Expires <= 0 {
86 return false
87 }
88 if (s.Compatibility != nil || s.Execution != nil) && s.Preflight == nil || s.Resume != nil && s.Execution == nil {
89 return false
90 }
91 for _, r := range []*maintenanceguard.Reference{s.Preflight, s.Compatibility, s.Execution, s.Resume} {
92 if r != nil && !workerRef(*r) {
93 return false
94 }
95 }
96 return true
97}
98
99// Canonical encoding is part of this new internal wire version. Comparing typed
100// encoding rejects aliases, duplicate/unknown fields and missing false fields.
101func decodeWorkerCanonical(raw []byte, dst any) error {
102 if len(raw) == 0 || len(raw) > MaxWorkerControlBytes {
103 return ErrWorkerControl
104 }
105 if json.Unmarshal(raw, dst) != nil {
106 return ErrWorkerControl
107 }
108 canonical, err := json.Marshal(dst)
109 if err != nil {
110 return ErrWorkerControl
111 }
112 var compact bytes.Buffer
113 if json.Compact(&compact, raw) != nil || !bytes.Equal(compact.Bytes(), canonical) {
114 return ErrWorkerControl
115 }
116 return nil
117}
118func signRootPayload(sg Signer, payload []byte) ([]byte, error) {
119 if sg.usable() != nil || sg.scope != nil || sg.grant != nil {
120 return nil, ErrWorkerControl
121 }
122 b64 := base64.StdEncoding.EncodeToString(payload)
123 signature := ed25519.Sign(sg.key, []byte(b64))
124 return json.MarshalIndent(signedWrapper{Payload: b64, Sig: base64.StdEncoding.EncodeToString(signature)}, "", " ")
125}
126func verifyWorkerPayload(pub ed25519.PublicKey, wire []byte) ([]byte, error) {
127 var wrapper signedWrapper
128 if decodeWorkerCanonical(wire, &wrapper) != nil || wrapper.Grant != nil || len(pub) != ed25519.PublicKeySize {
129 return nil, ErrWorkerControl
130 }
131 signature, err := base64.StdEncoding.Strict().DecodeString(wrapper.Sig)
132 if err != nil || base64.StdEncoding.EncodeToString(signature) != wrapper.Sig || len(signature) != ed25519.SignatureSize || !ed25519.Verify(pub, []byte(wrapper.Payload), signature) {
133 return nil, ErrWorkerControl
134 }
135 raw, err := base64.StdEncoding.Strict().DecodeString(wrapper.Payload)
136 if err != nil || len(raw) > MaxWorkerControlBytes/2 || base64.StdEncoding.EncodeToString(raw) != wrapper.Payload {
137 return nil, ErrWorkerControl
138 }
139 return raw, nil
140}
141func SignWorkerHead(sg Signer, h WorkerHead) ([]byte, error) {
142 if !h.Valid() {
143 return nil, ErrWorkerControl
144 }
145 raw, err := json.Marshal(h)
146 if err != nil {
147 return nil, ErrWorkerControl
148 }
149 return signRootPayload(sg, raw)
150}
151func VerifyWorkerHead(pub ed25519.PublicKey, wire []byte) (WorkerHead, error) {
152 var h WorkerHead
153 raw, err := verifyWorkerPayload(pub, wire)
154 if err != nil || decodeWorkerCanonical(raw, &h) != nil || !h.Valid() {
155 return WorkerHead{}, ErrWorkerControl
156 }
157 return h, nil
158}
159func SignWorkerAssignment(sg Signer, s WorkerAssignmentState) ([]byte, error) {
160 if !s.Valid() {
161 return nil, ErrWorkerControl
162 }
163 raw, err := json.Marshal(s)
164 if err != nil {
165 return nil, ErrWorkerControl
166 }
167 return signRootPayload(sg, raw)
168}
169func VerifyWorkerAssignment(pub ed25519.PublicKey, wire []byte) (WorkerAssignmentState, error) {
170 var s WorkerAssignmentState
171 raw, err := verifyWorkerPayload(pub, wire)
172 if err != nil || decodeWorkerCanonical(raw, &s) != nil || !s.Valid() {
173 return WorkerAssignmentState{}, ErrWorkerControl
174 }
175 return s, nil
176}
177