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 "os"
6
7 "github.com/carlosframework/platform/internal/hostbox"
8 "github.com/carlosframework/platform/internal/hostguard"
9 "github.com/carlosframework/platform/internal/release"
10 "github.com/carlosframework/platform/internal/rootstate"
11)
12
13type generationRetirement struct {
14 guard hostguard.Guard
15 journal *hostbox.LaunchJournal
16 kernel func(context.Context, release.GenerationReservation) (string, error)
17 retiredKernel func(context.Context, release.GenerationReservation, hostguard.FenceView) (string, error)
18}
19
20func (v generationRetirement) closure(ctx context.Context, r release.GenerationReservation) (bool, error) {
21 if v.guard.Root == nil || v.journal == nil || v.journal.Remote() || v.kernel == nil {
22 return false, os.ErrPermission
23 }
24 b := r.Binding
25 t := b.Target
26 in, err := v.guard.Root.Installation(ctx, t.Host, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp})
27 if err != nil {
28 return false, err
29 }
30 // Installation admits an older capture only when root retained it as
31 // retired. Rebind preserves this identity's account, enrollment and migration.
32 // Its old launch journal and kernel obligations still have to settle below.
33 if in.Incarnation != t.Incarnation || in.Stamp < t.Stamp {
34 return false, os.ErrPermission
35 }
36 in.Stamp = t.Stamp
37 if in.Host != b.Binding.Host || in.Account != b.Binding.Account || in.App != b.Binding.App || in.Box != t.Box || in.Enrollment != t.Enrollment || in.InventoryRevision == 0 || in.Record != b.Record || rootHistoryInstallation(in) != b.Installation {
38 return false, os.ErrPermission
39 }
40 launches, err := v.journal.GenerationLaunches(ctx)
41 if err != nil {
42 return false, err
43 }
44 matches, controllers := 0, 0
45 closed := false
46 writersClosed := false
47 for _, launch := range launches {
48 target := launch.Target
49 if target.Host != t.Host || target.Incarnation != t.Incarnation || target.Stamp != t.Stamp || target.Session != t.Session || target.Box != t.Box || target.Enrollment != t.Enrollment || target.BootID != t.BootID || target.ControllerID != t.ControllerID {
50 continue
51 }
52 controllers++
53 writersClosed = launch.WritersClosed
54 if launch.ControllerPID != b.ControllerPID || launch.ProcessStart != b.ControllerStart {
55 return false, os.ErrPermission
56 }
57 for _, unit := range launch.Reservations {
58 if unit.ID != b.Attempt {
59 continue
60 }
61 if !hostbox.IsUnitReservation(unit) || (unit.State != hostbox.UnitIntended && unit.State != hostbox.UnitStarted && unit.State != hostbox.UnitSettled) || !hostbox.SameLaunchOwner(unit.Target, target) || unit.Inputs != b.Inputs || unit.InvocationID != b.Invocation || unit.Executable.Device != b.Executable.Device || unit.Executable.Inode != b.Executable.Inode || unit.Group.RelativePath != generationBindingGroup(b) {
62 return false, os.ErrPermission
63 }
64 p := r.Process
65 if p != (release.GenerationProcess{}) && (unit.PID != p.PID || unit.ProcessStart != p.Start || unit.Group.RelativePath != p.Group || unit.Group.InvocationID != p.Invocation || unit.Group.Device != p.GroupIdentity.Device || unit.Group.Inode != p.GroupIdentity.Inode) {
66 return false, os.ErrPermission
67 }
68 matches++
69 closed = launch.WritersClosed && unit.State == hostbox.UnitSettled
70 }
71 }
72 if controllers == 1 && matches == 0 && r.Process == (release.GenerationProcess{}) {
73 if r.Closed && !writersClosed {
74 return false, os.ErrPermission
75 }
76 return writersClosed, nil
77 }
78 if controllers != 1 || matches != 1 || (r.Closed && !closed) {
79 return false, os.ErrPermission
80 }
81 return closed, nil
82}
83
84// closed verifies root's existing irreversible closure. It never stops a unit,
85// closes another controller's admission, or interprets a missing journal as join.
86func (v generationRetirement) closed(ctx context.Context, r release.GenerationReservation) error {
87 closed, err := v.closure(ctx, r)
88 if err != nil {
89 return err
90 }
91 if !closed {
92 return os.ErrPermission
93 }
94 return nil
95}
96func (v generationRetirement) verify(ctx context.Context, r release.GenerationReservation) (string, error) {
97 if err := v.closed(ctx, r); err != nil {
98 return "", err
99 }
100 evidence, err := v.kernel(ctx, r)
101 if err != nil {
102 return "", err
103 }
104 if err := v.closed(ctx, r); err != nil {
105 return "", err
106 }
107 return evidence, nil
108}
109
110// The caller holds release exclusion; host exclusion remains held through both
111// ledger commits and the kernel check, in the same order used by launch.
112func (v generationRetirement) retire(ctx context.Context, g *release.RootGenerations, r release.GenerationReservation) error {
113 if v.retiredKernel != nil {
114 t := r.Binding.Target
115 view, err := v.guard.Root.View(ctx, t.Host, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp})
116 if err != nil {
117 return err
118 }
119 v.kernel = func(ctx context.Context, current release.GenerationReservation) (string, error) {
120 return v.retiredKernel(ctx, current, view)
121 }
122 }
123 return v.guard.WithLocal(ctx, r.Binding.Binding.Host, func() error {
124 closed, err := v.closure(ctx, r)
125 if err != nil || !closed {
126 return err
127 }
128 return g.Retire(ctx, r, v.closed, v.verify)
129 })
130}
131
132func productionRetireGenerations(ctx context.Context, g *release.RootGenerations) error {
133 records, err := g.Reservations(ctx)
134 if err != nil {
135 return err
136 }
137 // Absence is not completeness. The pin reader still requires its verifier.
138 if len(records) == 0 {
139 return nil
140 }
141 state, err := rootstate.Open("/var/lib/carlos-claims/authority/fences", false, false)
142 if err != nil {
143 return err
144 }
145 defer state.Close()
146 journal, err := openGenerationLaunchInventory("/var/lib/carlos-claims/journals.db")
147 if err != nil {
148 return err
149 }
150 defer journal.Close()
151 verifier := generationRetirement{guard: hostguard.Guard{DataDir: dataDir(), Root: hostguard.NewRootAuthority(state)}, journal: journal, kernel: productionGenerationAbsence, retiredKernel: productionRetiredGenerationAbsence}
152 return verifier.retireAll(context.WithValue(ctx, generationCensusReservationsKey{}, records), g, records)
153}
154
155func (v generationRetirement) retireAll(ctx context.Context, g *release.RootGenerations, records []release.GenerationReservation) error {
156 for _, r := range records {
157 if r.Retirement != "" {
158 continue
159 }
160 if err := v.retire(ctx, g, r); err != nil {
161 return err
162 }
163 }
164 return nil
165}
166