package host import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "os" "strings" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" ) type rootRestartRequestSource struct { state rootstate.Store store release.Store census func() (rootparticipation.Census, error) } func (s rootRestartRequestSource) installation(ctx context.Context, host string) (hostguard.RootInstallation, error) { g := hostguard.Guard{Root: hostguard.NewRootAuthority(s.state)} captured, err := g.Capture(host) if err != nil { return hostguard.RootInstallation{}, err } if captured.Stamp == 0 { return hostguard.RootInstallation{Empty: true}, nil } v, err := g.Root.View(ctx, host, captured) if err != nil { return hostguard.RootInstallation{}, err } return s.checkInstallation(v.Installation) } func (s rootRestartRequestSource) launchInstallation(ctx context.Context, host string, b release.GenerationBinding) (hostguard.RootInstallation, error) { if host != b.Binding.Host || host != b.Target.Host { return hostguard.RootInstallation{}, os.ErrPermission } in, err := hostguard.NewRootAuthority(s.state).Installation(ctx, host, hostguard.Captured{Incarnation: b.Target.Incarnation, Stamp: b.Target.Stamp}) if err != nil { return in, err } if rootHistoryInstallation(in) != b.Installation || in.Account != b.Binding.Account || in.App != b.Binding.App || in.Record != b.Record || in.Box != b.Target.Box || in.Enrollment != b.Target.Enrollment { return in, os.ErrPermission } return s.checkInstallation(in) } func (s rootRestartRequestSource) checkInstallation(in hostguard.RootInstallation) (hostguard.RootInstallation, error) { c, err := s.census() if err != nil { return in, err } if !c.Complete || c.NeverEnrolled || in.Empty || in.Box != c.Box || in.Enrollment != c.Enrollment || in.Migration != c.Migration || in.InventoryRevision == 0 { return in, os.ErrPermission } return in, nil } func (s rootRestartRequestSource) target(ctx context.Context, in hostguard.RootInstallation) (release.RestartTarget, error) { if in.Record != provision.RecordKey(in.Account, in.App, in.Host) { return release.RestartTarget{}, os.ErrPermission } rec, revision, err := provision.GetRecordETag(ctx, s.store, in.Account, in.App, in.Host) if err != nil { return release.RestartTarget{}, err } inc, err := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt) if err != nil || inc != in.Incarnation { return release.RestartTarget{}, os.ErrPermission } // The agent owns exec children. Only an explicit canonical unit enters // root's unit journal; socket shape cannot invent a unit binding. if rec.Unit == "" { return release.RestartTarget{}, nil } rt, err := rootRecordRoute(rec) if err != nil { return release.RestartTarget{}, err } if !release.Adoptable(rt) || revision == "" { return release.RestartTarget{}, os.ErrPermission } raw, err := json.Marshal(in) if err != nil { return release.RestartTarget{}, err } id := sha256.Sum256(raw) return release.RestartTarget{Host: in.Host, Source: in.Record + "@" + revision, Installation: hex.EncodeToString(id[:]), Unit: rt.Unit, Environment: rec.Environment, Hibernate: rec.HibernateOrDefault()}, nil } func (s rootRestartRequestSource) inventory(ctx context.Context) (map[release.RestartBinding][]release.RestartTarget, error) { if s.state == nil || s.store == nil || s.census == nil { return nil, os.ErrPermission } c, err := s.census() if err != nil { return nil, err } if !c.Complete || c.NeverEnrolled { return nil, os.ErrPermission } names, err := s.state.Names() if err != nil { return nil, err } out := map[release.RestartBinding][]release.RestartTarget{} for _, name := range names { if strings.HasSuffix(name, ".lock") { continue } if !strings.HasSuffix(name, ".json") { return nil, os.ErrPermission } in, err := s.installation(ctx, strings.TrimSuffix(name, ".json")) if err != nil { return nil, err } if in.Empty { continue } // Sidecars use a different record family and have no root restart leg. if in.Record != provision.RecordKey(in.Account, in.App, in.Host) { acct, app, name, ok := provision.ParseSidecarKey(in.Record, map[string]bool{in.Account: true}) if ok && acct == in.Account && app == in.App && provision.ValidSidecarName(name) && provision.SidecarHost(name, app, acct) == in.Host { continue } return nil, os.ErrPermission } target, err := s.target(ctx, in) if err != nil { return nil, err } if target.Host == "" { continue } b := release.RestartBinding{Box: c.Box, Account: in.Account, App: in.App} out[b] = append(out[b], target) } return out, nil }