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 "bytes"
5 "context"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "io"
11 "os"
12 "path/filepath"
13 "strings"
14
15 "github.com/carlosframework/platform/internal/controlname"
16 "github.com/carlosframework/platform/internal/hostfloor"
17 "github.com/carlosframework/platform/internal/provision"
18 "github.com/carlosframework/platform/internal/release"
19 "github.com/carlosframework/platform/internal/rootstate"
20)
21
22const rootNamedUnits = "/etc/carlos/named-units.json"
23
24type namedUnit struct {
25 Host string `json:"host"`
26 Account string `json:"account,omitempty"`
27 App string `json:"app"`
28 Unit string `json:"unit"`
29 Addr string `json:"addr"`
30 Health *provision.Health `json:"health"`
31}
32
33type proofRecord struct {
34 record *provision.Record
35 key, revision, execPath string
36 local bool
37 files []proofUnitFile
38}
39
40func (s *proofBindingSource) trustedBytes(path string) ([]byte, proofUnitFile, error) {
41 f, err := s.openFile(path)
42 if err != nil {
43 return nil, proofUnitFile{}, err
44 }
45 defer f.Close()
46 if path == s.namedPath {
47 if err := rootstate.CheckPublicConfig(f); err != nil {
48 return nil, proofUnitFile{}, err
49 }
50 }
51 identity, err := rootstate.Identity(f)
52 if err != nil {
53 return nil, proofUnitFile{}, err
54 }
55 raw, err := io.ReadAll(io.LimitReader(f, rootstate.MaxRecord+1))
56 if err != nil {
57 return nil, proofUnitFile{}, err
58 }
59 if len(raw) > rootstate.MaxRecord {
60 return nil, proofUnitFile{}, os.ErrPermission
61 }
62 file := proofUnitFile{Path: path, Identity: identity}
63 if s.persistent != nil {
64 file.Persistent, err = s.persistent(f)
65 if err != nil {
66 return nil, proofUnitFile{}, err
67 }
68 }
69 return raw, file, nil
70}
71
72func (s *proofBindingSource) record(ctx context.Context, host string) (proofRecord, error) {
73 rec, etag, err := s.findRecord(ctx, host)
74 if err == nil {
75 return proofRecord{record: rec, key: provision.RecordKey(rec.Account, rec.App, host), revision: etag}, nil
76 }
77 if !errors.Is(err, os.ErrNotExist) || s.namedPath == "" {
78 return proofRecord{}, err
79 }
80 var selected *namedUnit
81 var evidence []proofUnitFile
82 hash := sha256.New()
83 add := func(path string) ([]byte, error) {
84 raw, file, err := s.trustedBytes(path)
85 if err != nil {
86 return nil, err
87 }
88 evidence = append(evidence, file)
89 identity := proofFileSource(file)
90 if file.Persistent.Boot == "" {
91 identity, _ = json.Marshal(struct {
92 Path string
93 Identity rootstate.FileIdentity
94 }{file.Path, file.Identity})
95 }
96 hash.Write(identity)
97 hash.Write(raw)
98 return raw, nil
99 }
100 raw, err := add(s.namedPath)
101 if err == nil {
102 var list struct {
103 Schema int `json:"schema"`
104 Units []namedUnit `json:"units"`
105 }
106 dec := json.NewDecoder(bytes.NewReader(raw))
107 dec.DisallowUnknownFields()
108 if dec.Decode(&list) != nil || dec.Decode(new(any)) != io.EOF || list.Schema != 1 {
109 return proofRecord{}, os.ErrPermission
110 }
111 for _, u := range list.Units {
112 if !controlname.ValidHostShape(u.Host) || !release.ValidApp(u.App) || (u.Account != "" && !release.ValidAccount(u.Account)) {
113 return proofRecord{}, os.ErrPermission
114 }
115 if u.Host == host {
116 if selected != nil {
117 return proofRecord{}, os.ErrPermission
118 }
119 copy := u
120 selected = &copy
121 }
122 }
123 } else if !errors.Is(err, os.ErrNotExist) {
124 return proofRecord{}, err
125 }
126 marker := filepath.Join(s.optDir, "system", "guard", "console-ready.json")
127 console, consoleErr := add(marker)
128 if consoleErr != nil && !errors.Is(consoleErr, os.ErrNotExist) {
129 return proofRecord{}, consoleErr
130 }
131 guarded := consoleErr == nil && string(console) == release.LiveLink(s.optDir, host)
132 execPath := release.LiveLink(s.optDir, host)
133 if guarded {
134 dropin := filepath.Join(s.unitDirs[0], "console.service.d", "90-host-floor.conf")
135 raw, err := add(dropin)
136 if err != nil || string(raw) != hostfloor.ConsoleGuardDropIn(string(console)) {
137 return proofRecord{}, os.ErrPermission
138 }
139 if selected == nil {
140 selected = &namedUnit{Host: host, App: "console", Unit: "console.service", Addr: "127.0.0.1:8090", Health: &provision.Health{Path: "/healthz"}}
141 }
142 if selected.Unit != "console.service" || selected.App != "console" || selected.Account != "" {
143 return proofRecord{}, os.ErrPermission
144 }
145 execPath = "/opt/carlos/system/guard/carlos"
146 }
147 if selected == nil {
148 return proofRecord{}, os.ErrNotExist
149 }
150 if selected.Health == nil || selected.Health.Path == "" || selected.Addr == "" || strings.HasPrefix(selected.Unit, "carlos-app@") {
151 return proofRecord{}, os.ErrPermission
152 }
153 rec = &provision.Record{Schema: 1, Host: host, Account: selected.Account, App: selected.App, Unit: selected.Unit, Kind: "instance", Addr: selected.Addr, Health: selected.Health}
154 return proofRecord{record: rec, key: s.namedPath, revision: hex.EncodeToString(hash.Sum(nil)), execPath: execPath, local: true, files: evidence}, nil
155}
156
157func proofExecPath(b proofBinding, opt string) string {
158 if b.ExecPath != "" {
159 return b.ExecPath
160 }
161 return release.LiveLink(opt, b.Route.Host)
162}
163
164func (s *proofBindingSource) findRecord(ctx context.Context, host string) (*provision.Record, string, error) {
165 if !s.cacheIndex {
166 return provision.FindRecordByHost(ctx, s.store, host)
167 }
168 s.indexMu.Lock()
169 defer s.indexMu.Unlock()
170 if s.index == nil && s.indexErr == nil {
171 s.index, s.indexErr = provision.IndexRecords(ctx, s.store)
172 }
173 if s.indexErr != nil {
174 return nil, "", s.indexErr
175 }
176 return s.index.Find(ctx, s.store, host)
177}
178