carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "os"
10
11 "github.com/carlosframework/platform/internal/hostbox"
12 "github.com/carlosframework/platform/internal/hostguard"
13 "github.com/carlosframework/platform/internal/provision"
14 "github.com/carlosframework/platform/internal/release"
15 "github.com/carlosframework/platform/internal/rootparticipation"
16 "github.com/carlosframework/platform/internal/rootstate"
17)
18
19type generationInventory struct {
20 Handovers []hostbox.GenerationHandover
21 Retirements []hostbox.RetirementJournal
22 Census rootparticipation.Census
23 Installations []hostguard.FenceView
24 Launches []hostbox.LaunchInventory
25 Payloads []hostbox.GenerationPayload
26 Records []generationRecord
27 Unlaunched map[string]string
28}
29type generationCensusReservationsKey struct{}
30
31// generationCensusParentKey carries the pid whose direct children the
32// generation census accepts as this command's own (see generationKernel's
33// parent). Only journal-migrate sets it.
34type generationCensusParentKey struct{}
35
36func generationCensusParent(ctx context.Context) int {
37 pid, _ := ctx.Value(generationCensusParentKey{}).(int)
38 return pid
39}
40
41type generationReconciler struct {
42 read func(context.Context) (generationInventory, error)
43 kernel func(context.Context) (string, string, error)
44}
45
46func generationInventoryDigest(v any) string {
47 raw, _ := json.Marshal(v)
48 sum := sha256.Sum256(raw)
49 return hex.EncodeToString(sum[:])
50}
51func generationOwner(t hostguard.FenceTarget) release.GenerationOwner {
52 return release.GenerationOwner{Host: t.Host, Incarnation: t.Incarnation, Stamp: t.Stamp, Session: t.Session, Box: t.Box, Enrollment: t.Enrollment, BootID: t.BootID, ControllerID: t.ControllerID}
53}
54func generationInstallation(v hostguard.FenceView, t release.GenerationOwner) (hostguard.RootInstallation, error) {
55 in := v.Installation
56 known := in.Incarnation == t.Incarnation && in.Stamp == t.Stamp
57 for _, old := range v.Retired {
58 if old.Incarnation == t.Incarnation && old.Stamp == t.Stamp {
59 known = true
60 }
61 }
62 if !known || in.Host != t.Host || in.Incarnation != t.Incarnation || in.Stamp < t.Stamp || in.Box != t.Box || in.Enrollment != t.Enrollment {
63 return in, os.ErrPermission
64 }
65 in.Stamp = t.Stamp
66 return in, nil
67}
68
69// Callers hold release exclusion through use. Recheck independent sources after
70// kernel inspection; a certificate never substitutes for the next inspection.
71func (v generationReconciler) reconcile(ctx context.Context, records []release.GenerationReservation) (release.GenerationEvidence, error) {
72 var zero release.GenerationEvidence
73 if v.read == nil || v.kernel == nil {
74 return zero, os.ErrPermission
75 }
76 before, err := v.read(ctx)
77 if err != nil {
78 return zero, err
79 }
80 c := before.Census
81 if c.Schema != 1 || !c.Complete || c.NeverEnrolled || (c.Pending != "" && !(c.Pending == "acquisition" && ctx.Value(launchRecoveryCensusKey{}) == true)) || c.Migration == 0 || c.Revision == 0 || c.LaunchRevision == 0 || c.Box == "" || c.Enrollment == "" {
82 return zero, os.ErrPermission
83 }
84 installs := map[string]hostguard.FenceView{}
85 for _, view := range before.Installations {
86 in := view.Installation
87 if _, exists := installs[in.Host]; exists || view.Schema != 1 || view.Host != in.Host || in.Box != c.Box || in.Enrollment != c.Enrollment || in.Migration != c.Migration || in.InventoryRevision == 0 || in.Record != provision.RecordKey(in.Account, in.App, in.Host) {
88 return zero, os.ErrPermission
89 }
90 installs[in.Host] = view
91 }
92 if err := generationPayloadBindings(before.Payloads, installs); err != nil {
93 return zero, err
94 }
95 physical, err := generationPhysicalPayloads(ctx, before)
96 if err != nil {
97 return zero, err
98 }
99 if err := generationPayloadFiles(ctx, dataDir(), physical); err != nil {
100 return zero, err
101 }
102 matched := make([]bool, len(records))
103 hosts := map[string]bool{}
104 for _, launch := range before.Launches {
105 t := generationOwner(launch.Target)
106 if _, err := generationInstallation(installs[t.Host], t); err != nil {
107 return zero, err
108 }
109 hosts[t.Host] = true
110 seen := map[string]bool{}
111 for _, unit := range launch.Reservations {
112 if seen[unit.ID] || unit.ID == "" || !hostbox.SameLaunchOwner(unit.Target, launch.Target) {
113 return zero, os.ErrPermission
114 }
115 seen[unit.ID] = true
116 if !hostbox.IsUnitReservation(unit) {
117 if !launch.WritersClosed || unit.State != "settled" {
118 return zero, os.ErrPermission
119 }
120 continue
121 }
122 count := 0
123 for i, r := range records {
124 b := r.Binding
125 if b.Target != t || b.Attempt != unit.ID {
126 continue
127 }
128 in, err := generationInstallation(installs[t.Host], t)
129 if err != nil {
130 return zero, err
131 }
132 if matched[i] || b.Binding.Host != in.Host || b.Binding.Account != in.Account || b.Binding.App != in.App || b.Record != in.Record || b.Installation != rootHistoryInstallation(in) || b.ControllerPID != launch.ControllerPID || b.ControllerStart != launch.ProcessStart || b.Inputs != unit.Inputs || b.Invocation != unit.InvocationID || b.Executable.Device != unit.Executable.Device || b.Executable.Inode != unit.Executable.Inode || unit.Group.RelativePath != generationBindingGroup(b) {
133 return zero, os.ErrPermission
134 }
135 if unit.State != hostbox.UnitIntended && unit.State != hostbox.UnitStarted && unit.State != hostbox.UnitSettled {
136 return zero, os.ErrPermission
137 }
138 p := r.Process
139 if p != (release.GenerationProcess{}) && (p.PID != unit.PID || p.Start != unit.ProcessStart || p.Group != unit.Group.RelativePath || p.Invocation != unit.Group.InvocationID || p.GroupIdentity.Device != unit.Group.Device || p.GroupIdentity.Inode != unit.Group.Inode) {
140 return zero, os.ErrPermission
141 }
142 if r.Closed && (!launch.WritersClosed || unit.State != hostbox.UnitSettled) {
143 return zero, os.ErrPermission
144 }
145 matched[i] = true
146 count++
147 }
148 // A closed legacy launch can predate E2, but kernel inspection must still
149 // exclude its references. An open launch always needs its exact root pin.
150 if count != 1 && !(count == 0 && launch.WritersClosed && unit.State == hostbox.UnitSettled) {
151 return zero, os.ErrPermission
152 }
153 }
154 }
155 for _, view := range before.Installations {
156 if !view.Installation.Empty && !hosts[view.Host] && before.Unlaunched[view.Host] != rootHistoryInstallation(view.Installation) {
157 return zero, os.ErrPermission
158 }
159 }
160 for i, r := range records {
161 if matched[i] {
162 continue
163 }
164 // A pre-intention reservation retains its exact controller journal.
165 in, err := generationInstallation(installs[r.Binding.Target.Host], r.Binding.Target)
166 if err != nil || r.Process != (release.GenerationProcess{}) || r.Binding.Installation != rootHistoryInstallation(in) || r.Binding.Binding.Account != in.Account || r.Binding.Binding.App != in.App || r.Binding.Record != in.Record || !hosts[in.Host] {
167 return zero, os.ErrPermission
168 }
169 pending := 0
170 for _, launch := range before.Launches {
171 if generationOwner(launch.Target) == r.Binding.Target && launch.ControllerPID == r.Binding.ControllerPID && launch.ProcessStart == r.Binding.ControllerStart {
172 if r.Closed && !launch.WritersClosed {
173 return zero, os.ErrPermission
174 }
175 pending++
176 }
177 }
178 if pending != 1 {
179 return zero, os.ErrPermission
180 }
181 }
182 snapshotDigest := generationInventoryDigest(before)
183 boot, kernel, err := v.kernel(context.WithValue(ctx, generationCensusReservationsKey{}, records))
184 if err != nil {
185 return zero, err
186 }
187 decoded, err := hex.DecodeString(kernel)
188 if err != nil || len(decoded) != sha256.Size || hex.EncodeToString(decoded) != kernel || boot == "" {
189 return zero, os.ErrPermission
190 }
191 after, err := v.read(ctx)
192 if err != nil {
193 return zero, err
194 }
195 if err := ctx.Err(); err != nil {
196 return zero, err
197 }
198 if snapshotDigest != generationInventoryDigest(after) {
199 return zero, os.ErrPermission
200 }
201 physical, err = generationPhysicalPayloads(ctx, after)
202 if err != nil {
203 return zero, err
204 }
205 if err := generationPayloadFiles(ctx, dataDir(), physical); err != nil {
206 return zero, err
207 }
208 return release.GenerationEvidence{Box: c.Box, Enrollment: c.Enrollment, Migration: c.Migration, Boot: boot, Installations: generationInventoryDigest([]any{before.Installations, before.Payloads, before.Records, before.Retirements, before.Handovers}), Launches: generationInventoryDigest([]any{c.LaunchRevision, before.Launches}), Kernel: kernel}, nil
209}
210
211func generationPayloadBindings(payloads []hostbox.GenerationPayload, installs map[string]hostguard.FenceView) error {
212 type key struct {
213 host, incarnation string
214 stamp uint64
215 }
216 seen := map[key]bool{}
217 for _, p := range payloads {
218 in := p.Inventory
219 view, ok := installs[in.Host]
220 captured := hostguard.Captured{Incarnation: in.Incarnation, Stamp: in.InstallationStamp}
221 current := captured.Incarnation == view.Installation.Incarnation && captured.Stamp == view.Installation.Stamp
222 known := current
223 for _, old := range view.Retired {
224 known = known || old == captured
225 }
226 k := key{in.Host, in.Incarnation, in.InstallationStamp}
227 if !ok || !known || seen[k] || !in.Complete || p.Revision == 0 || in.Box != view.Installation.Box || in.Enrollment != view.Installation.Enrollment || in.DirectoryRevision == "" {
228 return os.ErrPermission
229 }
230 if current && (p.Revision != view.Installation.InventoryRevision || view.Installation.Empty != (len(in.Entries) == 0)) {
231 return os.ErrPermission
232 }
233 for _, entry := range in.Entries {
234 if entry.Materialization != "materialized" && !(entry.Kind == "timer" && entry.Materialization == "intended") {
235 return os.ErrPermission
236 }
237 }
238 seen[k] = true
239 }
240 for host, view := range installs {
241 in := view.Installation
242 if !seen[key{host, in.Incarnation, in.Stamp}] {
243 return os.ErrPermission
244 }
245 // Rebind preserves predecessor payload obligations. A current empty
246 // installation cannot erase the inventory of an older retained stamp.
247 for _, old := range view.Retired {
248 if !seen[key{host, old.Incarnation, old.Stamp}] {
249 return os.ErrPermission
250 }
251 }
252 }
253 return nil
254}
255
256func productionGenerationInventory(ctx context.Context) (generationInventory, error) {
257 return readProductionGenerationInventory(ctx, false)
258}
259
260func productionLocalGenerationInventory(ctx context.Context) (generationInventory, error) {
261 return readProductionGenerationInventory(ctx, true)
262}
263
264func readProductionGenerationInventory(ctx context.Context, local bool) (generationInventory, error) {
265 snapshot, err := generationInventoryAt(ctx, rootparticipation.Namespace, "/var/lib/carlos-claims/authority/fences", "/var/lib/carlos-claims/journals.db")
266 if err != nil {
267 return generationInventory{}, err
268 }
269 snapshot.Handovers, err = productionGenerationHandovers(ctx)
270 if err != nil {
271 return generationInventory{}, err
272 }
273 snapshot.Retirements, err = productionGenerationRetirements(ctx)
274 if err != nil {
275 return generationInventory{}, err
276 }
277 if len(snapshot.Installations) == 0 {
278 return productionGenerationRegistryInventory(ctx, snapshot)
279 }
280 if local {
281 snapshot, err = generationLocalObligations(ctx, snapshot, productionGenerationCreation)
282 if err != nil {
283 return generationInventory{}, err
284 }
285 return productionGenerationRegistryInventory(ctx, snapshot)
286 }
287 store, err := openDeployStore(ctx)
288 if err != nil {
289 return generationInventory{}, err
290 }
291 snapshot, err = generationRequiredInventory(ctx, store, snapshot, productionGenerationCreation)
292 if err != nil {
293 return generationInventory{}, err
294 }
295 return productionGenerationRegistryInventory(ctx, snapshot)
296}
297
298func generationInventoryAt(ctx context.Context, censusPath, fencesPath, journalPath string) (generationInventory, error) {
299 var out generationInventory
300 censusState, err := rootstate.Open(censusPath, false, false)
301 census := rootparticipation.Ledger{State: censusState}
302 if err != nil {
303 return out, err
304 }
305 defer census.State.Close()
306 if ctx.Value(launchRecoveryCensusKey{}) == true {
307 out.Census, err = census.RecoveryCensus()
308 } else {
309 out.Census, err = census.Census()
310 }
311 if err != nil {
312 return out, err
313 }
314 state, err := rootstate.Open(fencesPath, false, false)
315 if err != nil {
316 return out, err
317 }
318 defer state.Close()
319 out.Installations, err = hostguard.NewRootAuthority(state).GenerationInstallations(ctx)
320 if err != nil {
321 return out, err
322 }
323 journal, err := openGenerationLaunchInventory(journalPath)
324 if err != nil {
325 return out, err
326 }
327 defer journal.Close()
328 out.Launches, err = journal.GenerationLaunches(ctx)
329 if err != nil {
330 return out, err
331 }
332 out.Payloads, err = journal.GenerationPayloads(ctx)
333 return out, err
334}
335
336func openGenerationLaunchInventory(path string) (*hostbox.LaunchJournal, error) {
337 for _, suffix := range []string{"", "-wal", "-shm"} {
338 file, err := rootstate.OpenAuthorityFile(path + suffix)
339 if suffix != "" && errors.Is(err, os.ErrNotExist) {
340 continue
341 }
342 if err != nil {
343 return nil, err
344 }
345 file.Close()
346 }
347 return hostbox.OpenGenerationLaunchReader(path)
348}
349