carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package launchctx
2
3import (
4 "bytes"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "io"
10 "strings"
11 "time"
12 "unicode/utf8"
13
14 "amadan.net/carlos/platform.git/launchctx/controlname"
15 "amadan.net/carlos/platform.git/launchctx/wireid"
16)
17
18const MaxLaunchBytes = 8192
19
20var ErrLaunch = errors.New("maintenance: invalid private launch context")
21
22// LaunchContext is private transport metadata, never proof of approval,
23// exclusion, lease ownership or a trusted worker invocation.
24type LaunchContext struct {
25 Version int `json:"version"`
26 Account string `json:"account"`
27 App string `json:"app"`
28 Host string `json:"host"`
29 RequestID string `json:"request_id"`
30 AttemptID string `json:"attempt_id"`
31 RequestDigest string `json:"request_digest"`
32 RecordIdentity string `json:"record_identity"`
33 ArtifactDigest string `json:"artifact_digest"`
34 EnvironmentEnvelopeDigest string `json:"environment_envelope_digest"`
35 GuardGeneration int64 `json:"guard_generation"`
36 InputSHA256 string `json:"input_sha256"`
37 DatabasePath string `json:"database_path"`
38 DatabaseDevice uint64 `json:"database_device"`
39 DatabaseInode uint64 `json:"database_inode"`
40 ExecutionDeadline string `json:"execution_deadline"`
41 ExecutionKind string `json:"execution_kind,omitempty"`
42 OperationID string `json:"operation_id,omitempty"`
43 ExpectedStateSHA256 string `json:"expected_state_sha256,omitempty"`
44 ClosedCheckpoint *ClosedCheckpoint `json:"closed_checkpoint,omitempty"`
45}
46
47type ClosedCheckpoint struct {
48 Version int `json:"version"`
49 PreparationID string `json:"preparation_id"`
50 CompletedAt string `json:"completed_at"`
51 MainLength int64 `json:"main_length"`
52 MainSHA256 string `json:"main_sha256"`
53 EvidenceSHA256 string `json:"evidence_sha256"`
54}
55
56// ReadLaunch requires EOF and consumes at most the bound plus one byte.
57// The worker/adapter must separately bound a reader that can block.
58func ReadLaunch(r io.Reader) (LaunchContext, error) {
59 if r == nil {
60 return LaunchContext{}, ErrLaunch
61 }
62 raw, err := io.ReadAll(io.LimitReader(r, MaxLaunchBytes+1))
63 if err != nil {
64 return LaunchContext{}, ErrLaunch
65 }
66 return DecodeLaunch(raw)
67}
68
69func DecodeLaunch(raw []byte) (LaunchContext, error) {
70 fail := func() (LaunchContext, error) { return LaunchContext{}, ErrLaunch }
71 if len(raw) == 0 || len(raw) > MaxLaunchBytes || !utf8.Valid(raw) || !validSurrogates(raw) {
72 return fail()
73 }
74 fields, err := launchObject(raw)
75 if err != nil {
76 return fail()
77 }
78 var version int
79 if json.Unmarshal(fields["version"], &version) != nil {
80 return fail()
81 }
82 names := strings.Fields("version account app host request_id attempt_id request_digest record_identity artifact_digest environment_envelope_digest guard_generation input_sha256 database_path database_device database_inode execution_deadline")
83 switch version {
84 case 1:
85 case 2:
86 names = append(names, strings.Fields("execution_kind operation_id expected_state_sha256 closed_checkpoint")...)
87 cp, err := launchObject(fields["closed_checkpoint"])
88 if err != nil || !exactLaunchFields(cp, strings.Fields("version preparation_id completed_at main_length main_sha256 evidence_sha256")) {
89 return fail()
90 }
91 default:
92 return fail()
93 }
94 if !exactLaunchFields(fields, names) {
95 return fail()
96 }
97 var c LaunchContext
98 if json.Unmarshal(raw, &c) != nil || !c.valid() {
99 return fail()
100 }
101 return c, nil
102}
103
104func EncodeLaunch(c LaunchContext) ([]byte, error) {
105 if !c.valid() {
106 return nil, ErrLaunch
107 }
108 raw, err := json.Marshal(c)
109 if err != nil || len(raw) > MaxLaunchBytes {
110 return nil, ErrLaunch
111 }
112 return raw, nil
113}
114
115func launchObject(raw []byte) (map[string]json.RawMessage, error) {
116 dec := json.NewDecoder(bytes.NewReader(raw))
117 tok, err := dec.Token()
118 if err != nil || tok != json.Delim('{') {
119 return nil, ErrLaunch
120 }
121 out := map[string]json.RawMessage{}
122 for dec.More() {
123 tok, err = dec.Token()
124 if err != nil {
125 return nil, ErrLaunch
126 }
127 key, ok := tok.(string)
128 if !ok {
129 return nil, ErrLaunch
130 }
131 if _, exists := out[key]; exists {
132 return nil, ErrLaunch
133 }
134 var value json.RawMessage
135 if dec.Decode(&value) != nil || bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
136 return nil, ErrLaunch
137 }
138 out[key] = value
139 }
140 tok, err = dec.Token()
141 if err != nil || tok != json.Delim('}') {
142 return nil, ErrLaunch
143 }
144 if _, err = dec.Token(); err != io.EOF {
145 return nil, ErrLaunch
146 }
147 return out, nil
148}
149func exactLaunchFields(fields map[string]json.RawMessage, names []string) bool {
150 if len(fields) != len(names) {
151 return false
152 }
153 for _, name := range names {
154 if _, ok := fields[name]; !ok {
155 return false
156 }
157 }
158 return true
159}
160func canonicalStamp(s string) (time.Time, bool) {
161 t, err := time.Parse(time.RFC3339Nano, s)
162 return t, err == nil && len(s) <= 30 && t.UTC().Format(time.RFC3339Nano) == s
163}
164func (c LaunchContext) valid() bool {
165 if !controlname.ValidAccount(c.Account) || !controlname.ValidApp(c.App) || !controlname.ValidHostShape(c.Host) {
166 return false
167 }
168 for _, id := range []string{c.RequestID, c.AttemptID} {
169 if !wireid.ValidID(id) {
170 return false
171 }
172 }
173 for _, digest := range []string{c.RequestDigest, c.RecordIdentity, c.ArtifactDigest, c.EnvironmentEnvelopeDigest, c.InputSHA256} {
174 if !wireid.ValidDigest(digest) {
175 return false
176 }
177 }
178 if c.GuardGeneration <= 0 || c.DatabaseDevice == 0 || c.DatabaseInode == 0 {
179 return false
180 }
181 if len(c.DatabasePath) == 0 || len(c.DatabasePath) > 1024 || c.DatabasePath[0] != '/' || strings.ContainsRune(c.DatabasePath, 0) || !utf8.ValidString(c.DatabasePath) {
182 return false
183 }
184 deadline, ok := canonicalStamp(c.ExecutionDeadline)
185 if !ok {
186 return false
187 }
188 switch c.Version {
189 case 1:
190 return c.ExecutionKind == "" && c.OperationID == "" && c.ExpectedStateSHA256 == "" && c.ClosedCheckpoint == nil
191 case 2:
192 if c.ExecutionKind != "compatibility-check" || !wireid.ValidID(c.OperationID) || !wireid.ValidDigest(c.ExpectedStateSHA256) || c.ClosedCheckpoint == nil {
193 return false
194 }
195 p := c.ClosedCheckpoint
196 completed, ok := canonicalStamp(p.CompletedAt)
197 if !ok || completed.After(deadline) || p.Version != 1 || !wireid.ValidID(p.PreparationID) || p.MainLength <= 0 || p.MainLength > 268435456 || !wireid.ValidDigest(p.MainSHA256) || !wireid.ValidDigest(p.EvidenceSHA256) {
198 return false
199 }
200 return p.EvidenceSHA256 == c.checkpointDigest()
201 }
202 return false
203}
204
205// CheckpointDigest correlates private bindings; it does not certify that a
206// checkpoint, writer exclusion or read-only namespace actually exists.
207func (c LaunchContext) CheckpointDigest() (string, error) {
208 if c.Version != 2 || c.ClosedCheckpoint == nil {
209 return "", ErrLaunch
210 }
211 return c.checkpointDigest(), nil
212}
213func (c LaunchContext) checkpointBytes() []byte {
214 p := c.ClosedCheckpoint
215 raw, _ := json.Marshal([]any{"carlos/closed-checkpoint/v1", c.Account, c.App, c.Host, c.OperationID, c.RequestID, c.AttemptID, c.RequestDigest, c.RecordIdentity, c.ArtifactDigest, c.EnvironmentEnvelopeDigest, c.GuardGeneration, c.InputSHA256, c.ExpectedStateSHA256, c.DatabasePath, c.DatabaseDevice, c.DatabaseInode, c.ExecutionDeadline, p.PreparationID, p.CompletedAt, p.MainLength, p.MainSHA256})
216 return raw
217}
218func (c LaunchContext) checkpointDigest() string {
219 sum := sha256.Sum256(c.checkpointBytes())
220 return hex.EncodeToString(sum[:])
221}
222