carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "errors"
8 "os"
9
10 "github.com/carlosframework/platform/internal/hostbox"
11 "github.com/carlosframework/platform/internal/hostguard"
12 "golang.org/x/sys/unix"
13)
14
15// The parent is established through a root-validated installation before any
16// namespace mutation. Startup can retain an obstruction using this evidence
17// even after the originating host has gone; it grants no artifact deletion.
18type servingParkParent struct {
19 BootID string `json:",omitempty"`
20 Version int
21 Parent servingArtifactDirectory
22}
23
24func (f *servingParkFS) namespaceParent() (servingParkParent, error) {
25 p, err := f.historicalNamespaceParent()
26 if err != nil {
27 return p, err
28 }
29 b, err := servingParkReadBinding(f.state, "namespace")
30 if err != nil || b == nil {
31 return p, err
32 }
33 if p.BootID != b.BeforeBoot || p.Parent != b.BeforeParent {
34 return p, hostbox.ErrChanged
35 }
36 p.BootID, p.Parent = b.BootID, b.Parent
37 return p, nil
38}
39
40func (f *servingParkFS) historicalNamespaceParent() (servingParkParent, error) {
41 var p servingParkParent
42 b, err := f.state.Read("namespace-parent.json")
43 if err != nil {
44 return p, err
45 }
46 if servingParkDecode(b, &p) != nil || p.Version != 1 || p.Parent.Device == 0 || p.Parent.Inode == 0 || p.Parent.Mount == 0 {
47 return p, hostbox.ErrChanged
48 }
49 return p, nil
50}
51
52// The caller holds namespace and host locks and has checked the claim, root
53// installation, inventory and stop evidence. Directory is never a wire path.
54func (f *servingParkFS) recordNamespaceParent(ctx context.Context, current servingArtifactIdentity, directory servingArtifactDirectory) error {
55 if err := ctx.Err(); err != nil {
56 return err
57 }
58 boot, err := f.currentBoot()
59 if err != nil {
60 return err
61 }
62 source, expected, err := f.source(servingParkAuthorization{Successor: current, Directory: directory})
63 if err != nil {
64 return err
65 }
66 defer source.Close()
67 parent, actual, err := f.parent()
68 if err != nil {
69 return err
70 }
71 defer parent.Close()
72 var st unix.Stat_t
73 if err = unix.Fstat(int(parent.Fd()), &st); err != nil {
74 return err
75 }
76 if actual != expected || st.Mode&0022 != 0 {
77 return hostbox.ErrChanged
78 }
79 p, err := f.namespaceParent()
80 if err == nil {
81 if p.Parent != expected || f.checkBoot(p.BootID) != nil {
82 return hostbox.ErrChanged
83 }
84 return nil
85 }
86 if !errors.Is(err, os.ErrNotExist) {
87 return err
88 }
89 // Existing private records constrain a first parent capture too. A new
90 // authenticated host cannot bless a replacement of an older mount parent.
91 for _, name := range []string{"namespace.json", "namespace-quarantine.json"} {
92 body, err := f.state.Read(name)
93 if errors.Is(err, os.ErrNotExist) {
94 continue
95 }
96 if err != nil {
97 return err
98 }
99 if name == "namespace.json" {
100 var n servingParkNamespace
101 if servingParkDecode(body, &n) != nil || n.Version != 1 || n.Parent != expected || n.Root.Inode == 0 || n.Root.Device != expected.Device || n.Root.Mount != expected.Mount || f.checkBoot(n.BootID) != nil {
102 return hostbox.ErrChanged
103 }
104 } else {
105 var q servingParkQuarantine
106 if servingParkDecode(body, &q) != nil || q.validate() != nil || q.Parent != expected || f.checkBoot(q.BootID) != nil {
107 return hostbox.ErrChanged
108 }
109 }
110 }
111 if err = ctx.Err(); err != nil {
112 return err
113 }
114 return (&servingParkLedger{state: f.state}).write("namespace-parent.json", servingParkParent{Version: 1, Parent: expected, BootID: boot})
115}
116
117// Called under the namespace lock. Absence of parent evidence never permits
118// recovery from a pathname alone. A normal empty installation needs no work.
119func (f *servingParkFS) recoverRecordedNamespace(ctx context.Context) error {
120 if err := ctx.Err(); err != nil {
121 return err
122 }
123 p, err := f.namespaceParent()
124 if errors.Is(err, os.ErrNotExist) {
125 return nil
126 }
127 if err != nil {
128 return err
129 }
130 if err := f.checkBoot(p.BootID); err != nil {
131 return err
132 }
133 parent, id, err := f.parent()
134 if err != nil {
135 return err
136 }
137 defer parent.Close()
138 var st unix.Stat_t
139 if err = unix.Fstat(int(parent.Fd()), &st); err != nil {
140 return err
141 }
142 if id != p.Parent || st.Mode&0022 != 0 {
143 return hostbox.ErrChanged
144 }
145 // An unfinished quarantine must replay before ordinary namespace opening.
146 if _, err = f.state.Read("namespace-quarantine.json"); err == nil {
147 _, err = f.recoverNamespace(ctx, p.Parent)
148 return err
149 } else if !errors.Is(err, os.ErrNotExist) {
150 return err
151 }
152 n, err := f.readNamespace()
153 if err == nil {
154 if n.Version != 1 || n.Parent != p.Parent || n.Root.Inode == 0 || n.Root.Device != id.Device || n.Root.Mount != id.Mount || f.checkBoot(n.BootID) != nil {
155 return hostbox.ErrChanged
156 }
157 root, rid, err := f.privateDir(parent, ".serving-parked")
158 if err == nil {
159 root.Close()
160 if rid == n.Root {
161 return nil
162 }
163 }
164 } else if errors.Is(err, os.ErrNotExist) {
165 root, err := servingParkOpenDir(parent, ".serving-parked")
166 if errors.Is(err, os.ErrNotExist) {
167 return nil
168 }
169 if err != nil {
170 return err
171 }
172 root.Close()
173 } else {
174 return err
175 }
176 _, err = f.recoverNamespace(ctx, p.Parent)
177 return err
178}
179
180func (p *servingParkRoot) prepareServingNamespace(ctx context.Context, a servingParkAuthorization, receipts []hostguard.FenceReceipt, legacy *servingLegacyEvidence) error {
181 fs, ok := p.ledger.fs.(*servingParkFS)
182 if !ok {
183 return hostbox.ErrUnsupported
184 }
185 if _, err := p.ledger.read(a.ID); errors.Is(err, os.ErrNotExist) {
186 if err := fs.room(); err != nil {
187 return err
188 }
189 if _, err := p.ledger.capture(ctx, a, receipts, legacy); err != nil {
190 return err
191 }
192 } else if err != nil {
193 return err
194 }
195 if err := fs.recordNamespaceParent(ctx, a.Successor, a.Directory); err != nil {
196 return err
197 }
198 return fs.recoverRecordedNamespace(ctx)
199}
200