carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostguard
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9
10 "github.com/carlosframework/platform/internal/controlname"
11)
12
13var (
14 ErrUnsupported = errors.New("hostguard: unsupported platform")
15 ErrNotProvisioned = errors.New("hostguard: directory not provisioned")
16 ErrUnsafeFile = errors.New("hostguard: expected regular file")
17 ErrInvalidHost = errors.New("hostguard: invalid host")
18 ErrHostTooLong = errors.New("hostguard: host too long for record or lock filename")
19 ErrNoRecord = errors.New("hostguard: no local record")
20 ErrMalformedRecord = errors.New("hostguard: malformed local record")
21)
22
23type Captured struct {
24 Incarnation string
25 Stamp uint64
26}
27
28type record struct {
29 migrationDenies []MigrationDeny
30 quarantined bool
31 // settled marks a quarantine whose only cause is root's terminal
32 // resolution of a pending intent: an obsolete binding (roster binding
33 // N3) or a claim proven to have superseded it. It never lifts the
34 // quarantine's execution refusal; the refusal-only migration census
35 // alone may inspect such an installation.
36 settled bool
37 root bool
38 installation RootInstallation
39 rootRetired []Captured
40 rebindCommit []byte
41 pendingInventory []byte
42 pendingInstallation *RootInstallation
43 barrier *FenceTarget
44 V int `json:"v"`
45 Host string `json:"host"`
46 Incarnation string `json:"incarnation"`
47 ActiveStamp uint64 `json:"active_stamp"`
48 RetiredStamps []uint64 `json:"retired_stamps"`
49 ActiveFence *FenceTarget `json:"active_fence,omitempty"`
50 FenceJournal []FenceReceipt `json:"fence_journal,omitempty"`
51}
52
53func (r record) validate(host string) error {
54 if r.V != 1 || r.Host != host || !controlname.ValidHostShape(host) || r.Incarnation == "" || (!r.root && len(r.RetiredStamps) > 32) {
55 return ErrMalformedRecord
56 }
57 var previous uint64
58 for _, s := range r.RetiredStamps {
59 if s == 0 || s <= previous || (r.ActiveStamp != 0 && s >= r.ActiveStamp) {
60 return ErrMalformedRecord
61 }
62 previous = s
63 }
64 if r.ActiveFence != nil {
65 if err := r.ActiveFence.validate(host); err != nil {
66 return err
67 }
68 }
69 for i := range r.FenceJournal {
70 if err := r.FenceJournal[i].validate(host); err != nil {
71 return err
72 }
73 if i > 0 && r.FenceJournal[i-1].Target == r.FenceJournal[i].Target {
74 return ErrMalformedRecord
75 }
76 }
77 return nil
78}
79
80func validateFileHost(host string) error {
81 if !controlname.ValidHostShape(host) {
82 return ErrInvalidHost
83 }
84 if len(host)+len(".json") > 255 || len(host)+len(".lock") > 255 {
85 return ErrHostTooLong
86 }
87 return nil
88}
89
90func readRecord(dataDir, host string) (record, error) {
91 if err := validateFileHost(host); err != nil {
92 return record{}, err
93 }
94 d, err := openRecordDir(dataDir)
95 if err != nil {
96 return record{}, err
97 }
98 defer d.close()
99 return d.read(host)
100}
101
102func decodeRecord(b []byte, host string) (record, error) {
103 d := json.NewDecoder(bytes.NewReader(b))
104 d.DisallowUnknownFields()
105 var r record
106 if err := d.Decode(&r); err != nil {
107 return record{}, fmt.Errorf("%w: %v", ErrMalformedRecord, err)
108 }
109 if _, err := d.Token(); err != io.EOF {
110 return record{}, ErrMalformedRecord
111 }
112 if err := r.validate(host); err != nil {
113 return record{}, err
114 }
115 return r, nil
116}
117
118func Capture(dataDir, host string) (Captured, error) {
119 r, err := readRecord(dataDir, host)
120 if err != nil {
121 return Captured{}, err
122 }
123 return Captured{Incarnation: r.Incarnation, Stamp: r.ActiveStamp}, nil
124}
125
126func writeRecord(dataDir string, r record) error {
127 if err := validateFileHost(r.Host); err != nil {
128 return err
129 }
130 if err := r.validate(r.Host); err != nil {
131 return err
132 }
133 d, err := openRecordDir(dataDir)
134 if err != nil {
135 return err
136 }
137 defer d.close()
138 return d.write(r)
139}
140