| index a3778611e..c6057687e 100644 |
| --- a/internal/host/movesfiles.go |
| +++ b/internal/host/movesfiles.go |
| @@ -13,6 +13,7 @@ import ( |
| "os" |
| "path/filepath" |
| "reflect" |
| + "slices" |
| "sort" |
| "strings" |
| |
| @@ -76,6 +77,11 @@ type movesFiles struct { |
| // FilesDir is the host's files directory and env file on this box |
| // (moveCfg.FilesDir). |
| FilesDir func(host string) (dir, envPath string) |
| + // Proof proves an origin against admissions the claim has recorded |
| + // (platform-1e's ruling, fix round 3): nil means chainOriginProof, the |
| + // retained admission chain, until claim Plan 15's durable |
| + // per-incarnation record slots in behind movesOriginProof. |
| + Proof movesOriginProof |
| // Evidence reads the claim pin's files evidence for a host, when the |
| // claim carries a pin: region HA's ReadPin over the op journal's pin |
| // reference. false means the claim carries no pin. nil means no pin |
| @@ -374,7 +380,7 @@ func (f *movesFiles) manifestVersion(ctx context.Context, key string) (string, e |
| // malformed or not (a malformed newest manifest refuses, never an older |
| // one in its place). found is false when no generation of the chain has a |
| // manifest. |
| -func (f *movesFiles) newestManifest(ctx context.Context, location string, chain []string) (hostfiles.Manifest, []string, bool, error) { |
| +func (f *movesFiles) newestManifest(ctx context.Context, o hostclaim.Observed, in hostclaim.Admission, location string, chain []string) (hostfiles.Manifest, []string, bool, error) { |
| for _, g := range chain { |
| prefix := movesFilesPrefix(location, g) |
| key := hostfiles.ManifestKey(prefix) |
| @@ -392,7 +398,7 @@ func (f *movesFiles) newestManifest(ctx context.Context, location string, chain |
| if !found { |
| return hostfiles.Manifest{}, nil, false, fmt.Errorf("host: the files manifest %s@%s is not readable", key, version) |
| } |
| - allowed, err := movesFilesAllowed(location, chain, m) |
| + allowed, err := f.movesFilesAllowed(ctx, o, in, location, chain, m) |
| if err != nil { |
| return hostfiles.Manifest{}, nil, false, fmt.Errorf("host: the files manifest %s@%s: %w", key, version, err) |
| } |
| @@ -401,34 +407,84 @@ func (f *movesFiles) newestManifest(ctx context.Context, location string, chain |
| return hostfiles.Manifest{}, nil, false, nil |
| } |
| |
| +// movesOriginProof proves that a generation of the holder's incarnation |
| +// was admitted, by the claim's own records (platform-1e's ruling, fix |
| +// round 3): provenance comes from the claim, never from a manifest, whose |
| +// writer holds the bucket credentials that could write its Origins too. |
| +// Today's implementation is chainOriginProof, the retained admission |
| +// chain; claim Plan 15's durable per-incarnation record (platform-1e's, |
| +// with a read API) slots in behind this interface. Anything unprovable |
| +// refuses: a host past the pruning window refuses until Plan 15 lands, |
| +// and that is accepted. |
| +type movesOriginProof interface { |
| + Proven(ctx context.Context, o hostclaim.Observed, in hostclaim.Admission, generation string) (bool, error) |
| +} |
| + |
| +// chainOriginProof proves a generation by the retained admission chain |
| +// (movesFilesChain: the holder's generation and each cold admission's |
| +// predecessor in this region), and the generation the admission in hand |
| +// opens: the claim's AdmitWriter CAS recorded that admission before the |
| +// start's Materialize runs. |
| +type chainOriginProof struct{ home, region string } |
| + |
| +func (p chainOriginProof) Proven(_ context.Context, o hostclaim.Observed, in hostclaim.Admission, generation string) (bool, error) { |
| + if generation != "" && generation == in.NewGeneration { |
| + return true, nil |
| + } |
| + if o.Claim == nil || o.Claim.Holder == nil { |
| + return false, nil |
| + } |
| + return slices.Contains(movesFilesChain(o.Claim.Holder, p.home, p.region), generation), nil |
| +} |
| + |
| +func (f *movesFiles) proof() movesOriginProof { |
| + if f.Proof != nil { |
| + return f.Proof |
| + } |
| + return chainOriginProof{home: f.HomeRegion, region: f.Region} |
| +} |
| + |
| // movesFilesAllowed is the origin set an admission allows for m, the |
| -// manifest it establishes from (fix round 2, finding 3; platform-1e's |
| -// ruling): the set m carries plus the generations admitted in the |
| -// current chain. An old manifest with no carried set is judged by the |
| -// chain alone. Every origin m's entries name must lie in it (a generation |
| -// never admitted and never carried is ErrOrigin, whatever its name), after |
| -// the location rule (movesFilesOrigins) and ValidateManifest. The new |
| -// manifest's set is this plus its own prefix (SetOrigins). |
| -func movesFilesAllowed(location string, chain []string, m hostfiles.Manifest) ([]string, error) { |
| +// manifest it establishes from: every origin m's entries name, every |
| +// origin m carries (Manifest.Origins, a hint and nothing more), and the |
| +// generations admitted in the current chain, each proven against the |
| +// claim's records (movesOriginProof) after the location rule |
| +// (movesFilesOrigins). A named or carried origin the claim cannot prove is |
| +// ErrOrigin, whatever its name and whether an entry uses it: a manifest |
| +// that adds a never-granted origin to its own set is refused. Then |
| +// ValidateManifest over the proven set. The new manifest's set is this plus |
| +// its own prefix (SetOrigins). |
| +func (f *movesFiles) movesFilesAllowed(ctx context.Context, o hostclaim.Observed, in hostclaim.Admission, location string, chain []string, m hostfiles.Manifest) ([]string, error) { |
| set := map[string]bool{} |
| for _, g := range chain { |
| set[movesFilesPrefix(location, g)] = true |
| } |
| - for _, o := range m.Origins { |
| - set[o] = true |
| - } |
| named, err := movesFilesOrigins(location, "", m) |
| if err != nil { |
| return nil, err |
| } |
| - for _, o := range named { |
| - if !set[o] { |
| - return nil, fmt.Errorf("%w: %s is neither admitted in the chain nor carried by the manifest", hostfiles.ErrOrigin, o) |
| + base := strings.TrimSuffix(location, "/") + "/files/g-" |
| + for _, origin := range append(append([]string(nil), named...), m.Origins...) { |
| + if set[origin] { |
| + continue |
| + } |
| + rest, under := strings.CutPrefix(origin, base) |
| + gen, slash := strings.CutSuffix(rest, "/") |
| + if !under || !slash || !movesFilesGeneration(gen) { |
| + return nil, fmt.Errorf("%w: %s is not a generation's files prefix of %s", hostfiles.ErrOrigin, origin, location) |
| + } |
| + proven, err := f.proof().Proven(ctx, o, in, gen) |
| + if err != nil { |
| + return nil, fmt.Errorf("prove the origin %s: %w", origin, err) |
| + } |
| + if !proven { |
| + return nil, fmt.Errorf("%w: %s is not proven by the claim's recorded admissions", hostfiles.ErrOrigin, origin) |
| } |
| + set[origin] = true |
| } |
| allowed := make([]string, 0, len(set)) |
| - for o := range set { |
| - allowed = append(allowed, o) |
| + for origin := range set { |
| + allowed = append(allowed, origin) |
| } |
| sort.Strings(allowed) |
| if err := hostfiles.ValidateManifest(m, allowed); err != nil { |
| @@ -490,7 +546,7 @@ func movesFilesOrigins(location, prefix string, m hostfiles.Manifest) ([]string, |
| // establishment of them (a retry after a crash past the commit: the same |
| // entries, the env bytes on disk, no adoption item, which the publisher |
| // then leaves as it is). Anything else refuses before any write. |
| -func (f *movesFiles) openPrepared(ctx context.Context, files release.Store, o hostclaim.Observed, gen, prefix string, env []byte) (*hostfiles.Publisher, error) { |
| +func (f *movesFiles) openPrepared(ctx context.Context, files release.Store, o hostclaim.Observed, in hostclaim.Admission, gen, prefix string, env []byte) (*hostfiles.Publisher, error) { |
| if f.DataDir == "" { |
| return nil, hostwrite.ErrUnavailable |
| } |
| @@ -525,7 +581,7 @@ func (f *movesFiles) openPrepared(ctx context.Context, files release.Store, o ho |
| if len(prepared.Origins) == 0 { |
| prepared.Origins = []string{prefix} |
| } |
| - allowed, err := movesFilesAllowed(h.Location, append(movesFilesChain(h, f.HomeRegion, f.Region), gen), prepared) |
| + allowed, err := f.movesFilesAllowed(ctx, o, in, h.Location, append(movesFilesChain(h, f.HomeRegion, f.Region), gen), prepared) |
| if err != nil { |
| return nil, fmt.Errorf("host: the prepared files manifest %s@%s: %w", key, pm.Version, err) |
| } |
| @@ -729,7 +785,7 @@ func (f *movesFiles) Establish(ctx context.Context, o hostclaim.Observed, in Cla |
| var p *hostfiles.Publisher |
| switch { |
| case in.Prepared: |
| - p, err = f.openPrepared(ctx, files, o, gen, prefix, env) |
| + p, err = f.openPrepared(ctx, files, o, in.Admission, gen, prefix, env) |
| if err != nil { |
| return err |
| } |
| @@ -740,7 +796,7 @@ func (f *movesFiles) Establish(ctx context.Context, o hostclaim.Observed, in Cla |
| } |
| p.SetOrigins([]string{prefix}) |
| default: |
| - base, origins, found, err := f.newestManifest(ctx, h.Location, movesFilesChain(h, f.HomeRegion, f.Region)) |
| + base, origins, found, err := f.newestManifest(ctx, o, in.Admission, h.Location, movesFilesChain(h, f.HomeRegion, f.Region)) |
| if err != nil { |
| return err |
| } |