| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "maps" |
| 6 | "reflect" |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | "time" |
| 10 | |
| 11 | "github.com/carlosframework/platform/internal/hostbox" |
| 12 | "github.com/carlosframework/platform/internal/hostclaim" |
| 13 | "github.com/carlosframework/platform/internal/hostguard" |
| 14 | "github.com/carlosframework/platform/internal/hostops" |
| 15 | "github.com/carlosframework/platform/internal/park" |
| 16 | ) |
| 17 | |
| 18 | func (p productionClaimRegional) ordinaryHome(ctx context.Context, o hostclaim.Observed) (hostbox.Actor, string, error) { |
| 19 | a, err := p.Identity.Verify(ctx) |
| 20 | if err != nil { |
| 21 | return a, "", err |
| 22 | } |
| 23 | if o.Absent || o.ETag == "" || o.Claim == nil || o.Host != o.Claim.Host || o.Claim.Validate() != nil || p.Local == nil || p.Local.assembly == nil { |
| 24 | return a, "", park.ErrUnsafe |
| 25 | } |
| 26 | home, err := park.VerifiedHome(a.FleetHomeRegion, a.HomeRegion, a.Region, o.Claim.Op.Intent["home_region"]) |
| 27 | if err != nil || a.FleetHomeRegion == "" || a.Region != a.FleetHomeRegion { |
| 28 | return a, "", park.ErrUnsafe |
| 29 | } |
| 30 | switch o.Claim.Op.Kind { |
| 31 | case hostclaim.KindServing, hostclaim.KindDeleting, hostclaim.KindParking: |
| 32 | default: |
| 33 | return a, "", park.ErrUnsafe |
| 34 | } |
| 35 | if h := o.Claim.Holder; h != nil { |
| 36 | if h.Location != "instances/"+o.Host+"/"+h.Incarnation || (o.Claim.Op.Exec != "" && o.Claim.Op.Exec != a.Box) { |
| 37 | return a, "", park.ErrUnsafe |
| 38 | } |
| 39 | for _, admission := range h.Admissions { |
| 40 | if admission.Box != a.Box || !park.SameRegion(home, admission.Region, a.Region) || (admission.PredecessorRegion != "" && !park.SameRegion(home, admission.PredecessorRegion, a.Region)) { |
| 41 | return a, "", park.ErrUnsafe |
| 42 | } |
| 43 | } |
| 44 | } |
| 45 | return a, home, nil |
| 46 | } |
| 47 | |
| 48 | func (p productionClaimRegional) ordinaryView(ctx context.Context, o hostclaim.Observed) (park.View, error) { |
| 49 | a, home, err := p.ordinaryHome(ctx, o) |
| 50 | if err != nil { |
| 51 | return park.View{}, err |
| 52 | } |
| 53 | v := park.View{HomeRegion: home, ClaimETag: o.ETag, Region: park.PlacementRegion(a.Region), AuthoritativeRegion: movesNoLiveRegion, |
| 54 | ArchivedIncarnations: claimArchivedLocations(o.Claim), RefusedLocations: map[string]error{"moves/" + o.Host: park.ErrUnsafe}} |
| 55 | h := o.Claim.Holder |
| 56 | if h == nil { |
| 57 | return v, nil |
| 58 | } |
| 59 | v.GenerationOf = func(r park.Ref) (string, bool) { return landingGenerationOf(h.Location, r.Key) } |
| 60 | if o.Claim.Op.Kind == hostclaim.KindParking { |
| 61 | return v, nil |
| 62 | } |
| 63 | v.AuthoritativeRegion = a.Region |
| 64 | data := hostbox.RequestStore{Store: p.Local.assembly.Data, Check: claimBoundIdentity(p.Identity.Verify, a)} |
| 65 | listing, err := data.ListVersions(ctx, a.Bucket, h.Location+"/") |
| 66 | if err != nil { |
| 67 | return park.View{}, err |
| 68 | } |
| 69 | if err = readMarker(ctx, data, a.Bucket, o.Host, h.Location, *h, listing, &v); err != nil { |
| 70 | return park.View{}, err |
| 71 | } |
| 72 | databasePins := maps.Clone(v.Pinned) |
| 73 | if err = pinFilesLiveSet(ctx, data, a.Bucket, h.Location, listing, &v, liveGenerations(o.Claim, v)...); err != nil { |
| 74 | return park.View{}, err |
| 75 | } |
| 76 | v.FilePins = map[park.Ref]bool{} |
| 77 | for ref := range v.Pinned { |
| 78 | if !databasePins[ref] && strings.HasPrefix(ref.Key, h.Location+"/files/") { |
| 79 | v.FilePins[ref] = true |
| 80 | } |
| 81 | } |
| 82 | return v, nil |
| 83 | } |
| 84 | |
| 85 | func (p productionClaimRegional) VerifyObligation(ctx context.Context, o hostclaim.Observed, ob hostclaim.Obligation) (hostclaim.ParkScope, error) { |
| 86 | if p.Parks != nil { |
| 87 | if evidence, ok := p.Parks.Evidence.(hostbox.RegionalObligationEvidence); ok { |
| 88 | return evidence.VerifyObligation(ctx, o, ob) |
| 89 | } |
| 90 | return hostclaim.ParkScope{}, park.ErrUnsafe |
| 91 | } |
| 92 | a, home, err := p.ordinaryHome(ctx, o) |
| 93 | if err != nil { |
| 94 | return hostclaim.ParkScope{}, err |
| 95 | } |
| 96 | c, h := o.Claim, o.Claim.Holder |
| 97 | if c.Op.Kind != hostclaim.KindServing || c.Op.ID != "" || h == nil || ob.Kind != "park-generation" || ob.Owner != "region:"+ob.Region || !park.SameRegion(home, ob.Region, a.Region) || ob.Box != "" || ob.ParkTarget != "" || len(ob.EvidenceRefs) != 0 || ob.Generation == "" || ob.Generation == h.Generation { |
| 98 | return hostclaim.ParkScope{}, park.ErrUnsafe |
| 99 | } |
| 100 | session, err := strconv.ParseUint(strings.TrimPrefix(ob.Op, "session:"), 10, 64) |
| 101 | if err != nil || ob.Op != "session:"+strconv.FormatUint(session, 10) || session == 0 || session > h.Session { |
| 102 | return hostclaim.ParkScope{}, park.ErrUnsafe |
| 103 | } |
| 104 | found := false |
| 105 | for _, live := range c.Obligations { |
| 106 | live.Park = ob.Park |
| 107 | if reflect.DeepEqual(live, ob) { |
| 108 | found = true |
| 109 | } |
| 110 | } |
| 111 | if !found { |
| 112 | return hostclaim.ParkScope{}, park.ErrChanged |
| 113 | } |
| 114 | guard := p.Local.assembly.Guard |
| 115 | if guard.Root == nil && guard.FenceView == nil { |
| 116 | return hostclaim.ParkScope{}, park.ErrUnsafe |
| 117 | } |
| 118 | var at time.Time |
| 119 | for _, admission := range h.Admissions { |
| 120 | if admission.Generation != ob.Generation { |
| 121 | continue |
| 122 | } |
| 123 | if admission.Mode != "cold" || admission.Session >= session { |
| 124 | return hostclaim.ParkScope{}, park.ErrUnsafe |
| 125 | } |
| 126 | receipts, err := guard.CompletedServingStops(ctx, o.Host, hostguard.Captured{Incarnation: h.Incarnation, Stamp: admission.Session}, admission.Session, a.Box) |
| 127 | if err != nil { |
| 128 | return hostclaim.ParkScope{}, err |
| 129 | } |
| 130 | for _, receipt := range receipts { |
| 131 | if !park.SameRegion(home, receipt.Target.Region, a.Region) || receipt.Target.Enrollment != a.Enrollment || receipt.EvidenceAt.IsZero() || verifyRetirementReceipt(ctx, receipt) != nil { |
| 132 | return hostclaim.ParkScope{}, park.ErrUnsafe |
| 133 | } |
| 134 | if ob.Park != nil { |
| 135 | if receipt.EvidenceAt.Equal(ob.Park.EvidenceAt) { |
| 136 | at = receipt.EvidenceAt |
| 137 | } |
| 138 | } else if receipt.EvidenceAt.After(at) { |
| 139 | at = receipt.EvidenceAt |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | if at.IsZero() { |
| 144 | return hostclaim.ParkScope{}, park.ErrUnsafe |
| 145 | } |
| 146 | scope := hostclaim.ParkScope{Location: h.Location, Incarnation: h.Incarnation, EvidenceAt: at, RequestLimit: hostbox.RequestLimit} |
| 147 | if ob.Park != nil && !reflect.DeepEqual(*ob.Park, scope) { |
| 148 | return hostclaim.ParkScope{}, park.ErrChanged |
| 149 | } |
| 150 | return scope, scope.Validate(o.Host) |
| 151 | } |
| 152 | |
| 153 | func (p productionClaimRegional) authorizeOrdinaryRetirement(ctx context.Context, o hostclaim.Observed, ob hostclaim.Obligation) error { |
| 154 | a, _, err := p.ordinaryHome(ctx, o) |
| 155 | if err != nil { |
| 156 | return err |
| 157 | } |
| 158 | if o.Claim.Op.Kind != hostclaim.KindParking || o.Claim.Holder == nil || ob.Kind != "park-replica" || p.Local.assembly.RetirementAuthority == nil { |
| 159 | return park.ErrUnsafe |
| 160 | } |
| 161 | if ob.ParkTarget != o.Claim.Holder.Location { |
| 162 | if ob.ParkTarget != "moves/"+o.Host { |
| 163 | return park.ErrUnsafe |
| 164 | } |
| 165 | data := hostbox.RequestStore{Store: p.Local.assembly.Data, Check: claimBoundIdentity(p.Identity.Verify, a)} |
| 166 | versions, err := data.ListVersions(ctx, a.Bucket, ob.ParkTarget+"/") |
| 167 | if err != nil { |
| 168 | return err |
| 169 | } |
| 170 | for _, version := range versions { |
| 171 | if version.Key != ob.ParkTarget+"/lock.json" || version.VersionID == "" { |
| 172 | return park.ErrUnsafe |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | return (hostops.RetirementEvidence{Authority: *p.Local.assembly.RetirementAuthority}).VerifyBound(ctx, o, ob) |
| 177 | } |
| 178 | |