package host import ( "bytes" "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "io" "os" "path/filepath" "strings" "github.com/carlosframework/platform/internal/controlname" "github.com/carlosframework/platform/internal/hostfloor" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootstate" ) const rootNamedUnits = "/etc/carlos/named-units.json" type namedUnit struct { Host string `json:"host"` Account string `json:"account,omitempty"` App string `json:"app"` Unit string `json:"unit"` Addr string `json:"addr"` Health *provision.Health `json:"health"` } type proofRecord struct { record *provision.Record key, revision, execPath string local bool files []proofUnitFile } func (s *proofBindingSource) trustedBytes(path string) ([]byte, proofUnitFile, error) { f, err := s.openFile(path) if err != nil { return nil, proofUnitFile{}, err } defer f.Close() if path == s.namedPath { if err := rootstate.CheckPublicConfig(f); err != nil { return nil, proofUnitFile{}, err } } identity, err := rootstate.Identity(f) if err != nil { return nil, proofUnitFile{}, err } raw, err := io.ReadAll(io.LimitReader(f, rootstate.MaxRecord+1)) if err != nil { return nil, proofUnitFile{}, err } if len(raw) > rootstate.MaxRecord { return nil, proofUnitFile{}, os.ErrPermission } file := proofUnitFile{Path: path, Identity: identity} if s.persistent != nil { file.Persistent, err = s.persistent(f) if err != nil { return nil, proofUnitFile{}, err } } return raw, file, nil } func (s *proofBindingSource) record(ctx context.Context, host string) (proofRecord, error) { rec, etag, err := s.findRecord(ctx, host) if err == nil { return proofRecord{record: rec, key: provision.RecordKey(rec.Account, rec.App, host), revision: etag}, nil } if !errors.Is(err, os.ErrNotExist) || s.namedPath == "" { return proofRecord{}, err } var selected *namedUnit var evidence []proofUnitFile hash := sha256.New() add := func(path string) ([]byte, error) { raw, file, err := s.trustedBytes(path) if err != nil { return nil, err } evidence = append(evidence, file) identity := proofFileSource(file) if file.Persistent.Boot == "" { identity, _ = json.Marshal(struct { Path string Identity rootstate.FileIdentity }{file.Path, file.Identity}) } hash.Write(identity) hash.Write(raw) return raw, nil } raw, err := add(s.namedPath) if err == nil { var list struct { Schema int `json:"schema"` Units []namedUnit `json:"units"` } dec := json.NewDecoder(bytes.NewReader(raw)) dec.DisallowUnknownFields() if dec.Decode(&list) != nil || dec.Decode(new(any)) != io.EOF || list.Schema != 1 { return proofRecord{}, os.ErrPermission } for _, u := range list.Units { if !controlname.ValidHostShape(u.Host) || !release.ValidApp(u.App) || (u.Account != "" && !release.ValidAccount(u.Account)) { return proofRecord{}, os.ErrPermission } if u.Host == host { if selected != nil { return proofRecord{}, os.ErrPermission } copy := u selected = © } } } else if !errors.Is(err, os.ErrNotExist) { return proofRecord{}, err } marker := filepath.Join(s.optDir, "system", "guard", "console-ready.json") console, consoleErr := add(marker) if consoleErr != nil && !errors.Is(consoleErr, os.ErrNotExist) { return proofRecord{}, consoleErr } guarded := consoleErr == nil && string(console) == release.LiveLink(s.optDir, host) execPath := release.LiveLink(s.optDir, host) if guarded { dropin := filepath.Join(s.unitDirs[0], "console.service.d", "90-host-floor.conf") raw, err := add(dropin) if err != nil || string(raw) != hostfloor.ConsoleGuardDropIn(string(console)) { return proofRecord{}, os.ErrPermission } if selected == nil { selected = &namedUnit{Host: host, App: "console", Unit: "console.service", Addr: "127.0.0.1:8090", Health: &provision.Health{Path: "/healthz"}} } if selected.Unit != "console.service" || selected.App != "console" || selected.Account != "" { return proofRecord{}, os.ErrPermission } execPath = "/opt/carlos/system/guard/carlos" } if selected == nil { return proofRecord{}, os.ErrNotExist } if selected.Health == nil || selected.Health.Path == "" || selected.Addr == "" || strings.HasPrefix(selected.Unit, "carlos-app@") { return proofRecord{}, os.ErrPermission } rec = &provision.Record{Schema: 1, Host: host, Account: selected.Account, App: selected.App, Unit: selected.Unit, Kind: "instance", Addr: selected.Addr, Health: selected.Health} return proofRecord{record: rec, key: s.namedPath, revision: hex.EncodeToString(hash.Sum(nil)), execPath: execPath, local: true, files: evidence}, nil } func proofExecPath(b proofBinding, opt string) string { if b.ExecPath != "" { return b.ExecPath } return release.LiveLink(opt, b.Route.Host) } func (s *proofBindingSource) findRecord(ctx context.Context, host string) (*provision.Record, string, error) { if !s.cacheIndex { return provision.FindRecordByHost(ctx, s.store, host) } s.indexMu.Lock() defer s.indexMu.Unlock() if s.index == nil && s.indexErr == nil { s.index, s.indexErr = provision.IndexRecords(ctx, s.store) } if s.indexErr != nil { return nil, "", s.indexErr } return s.index.Find(ctx, s.store, host) }