Repository navigation
Allowlist snapshot storage location path segments - #2784
Dmitry Berkovich (dberkov) merged 3 commits into
Conversation
|
cc ILLUM1N0X |
Abhishek Sharma (abhisheksharma2411)
left a comment
There was a problem hiding this comment.
The layering reads well, and I like that prefix and actorSnapshotURI keep the unvalidated path so recorded URIs still parse and anything already stored under a bad location stays deletable. That's the part that's easy to get wrong when tightening a validator.
I had a poke at the segment matcher. Everything I expected it to catch, it caught: case, surrounding dots, surrounding spaces, %61tespaces, atespaces%2Fteam-a, and a/../atespaces/x are all rejected.
One gap. The trim set is ". ", so non-ASCII spaces go straight through:
gs://b/root/atespaces\u00a0/x -> allowed (no-break space)
gs://b/root/atespaces\u2000/x -> allowed (en quad)
gs://b/root/atespaces\u3000/x -> allowed (ideographic space)
Tab and newline do get rejected, but only because net/url refuses control characters - not by this rule.
Since the doc comment says the trimming is there so "a backend that folds or trims names cannot turn a near-match into the real one", would unicode.IsSpace be a better trim predicate than the literal ". "? Low stakes on GCS, where names are byte-literal, so it needs a backend that normalises - it's just the same class the trim already defends against.
1095b79 to
de1baf0
Compare
|
Thanks, this should go in before GA. |
ILLUM1N0X
left a comment
There was a problem hiding this comment.
This needs a rebase. Main moved the proto to api/ateapi/v1 and renamed ateapipb to ateapi, so the proto comment and the tests will conflict.
On GCS, main sends every snapshot transfer through the exchange, so an actor whose snapshot is under a bad location can't resume after this change, because actorscope.FromActor now fails for it. That includes an actor that only borrows the old template's golden snapshot, so repointing it as in step 2 doesn't help either. Could you update the description and migration steps, and return FailedPrecondition from the exchange instead of Internal?
| return nil, fmt.Errorf(`invalid snapshot location %q: must not contain '\'`, location) | ||
| } | ||
| for segment := range strings.SplitSeq(u.Path, "/") { | ||
| if segment != "" && strings.Trim(segment, ". ") == "" { |
There was a problem hiding this comment.
This check still trims only ASCII spaces, so a segment that is just a no-break space passes. Could it use isDotOrSpace like the atespaces check?
There was a problem hiding this comment.
I think this is right as is. parseStorageURI is called on the garbage collection / reading path (ParseSnapshotURI, ParseStoragePrefix, SnapshotOwner.Prefix). If you tighten parseStorageURI to reject Unicode spaces or other non-ASCII characters, any historical snapshot created under a previously legal location that contained non-ASCII characters could never be deleted or collected by the GC workflow. The new restrictions make sure new bad paths can't be set.
| // TestParseSnapshotURIUnderAtespacesLocation covers a URI recorded under a | ||
| // location ValidateSnapshotLocation rejects: it is read and its owner's | ||
| // prefix can be collected, but nothing new is built under that location. | ||
| func TestParseSnapshotURIUnderAtespacesLocation(t *testing.T) { |
There was a problem hiding this comment.
Most of this is already covered by rows in TestParseSnapshotURI, TestNewActorSnapshotURI and TestSnapshotOwnerPrefix. Could the owner prefix check be a wantOwnerPrefix field in TestParseSnapshotURI instead?
de1baf0 to
eb9eb3b
Compare
|
ILLUM1N0X PTAL! |
| if segment != "" && trimmed == "" { | ||
| return fmt.Errorf("invalid snapshot location %q: %w: must not contain path segments of only dots and white space", location, ErrInvalidLocationSegment) | ||
| } | ||
| if strings.EqualFold(trimmed, atespacesPathSegment) { |
There was a problem hiding this comment.
I was expecting this PR to be just this line. IIUC, one of the reasons this PR got larger is that ParseStoragePrefix calls ValidateSnapshotLocation, but that seems wrong to me as both are different things? I'm not even convinced StoragePrefix is an abstraction we need to formalize either TBH.
WRT unit tests, I don't think we need to test this at every single layer? I think we should have a test that asserts that ValidateCreateActorTemplateRequest returns an error, which you already have.
There was a problem hiding this comment.
I kept the new check in ValidateSnapshotLocation only. ParseStoragePrefix and SnapshotOwner.Prefix check just the URI shape again, so stored snapshots still parse and get cleaned up, and the per-layer tests are gone: the rule is tested in validate_test.go and at template create in actor_template_test.go.
One behavior change follows from checkTagCreatable already calling ValidateSnapshotLocation: creating a tag from a template stored with such a location now fails with FAILED_PRECONDITION, so the existing CreateTag row expects that. Suspend and resume work as on main.
2377124 to
ad4e040
Compare
|
Julian Gutierrez Oschmann (@juli4n) Greg Castle (@destijl) this is down to the check in |
487f51f to
0d9481d
Compare
| if err != nil { | ||
| return err | ||
| } | ||
| for segment := range strings.SplitSeq(u.Path, "/") { |
There was a problem hiding this comment.
This denylists near-matches of atespaces, so it has to predict every normalization a backend might apply, and other dot- or space-bearing names still reach the backend. Could we allowlist segments instead (for example ^[A-Za-z0-9_-]+$, plus the case-insensitive atespaces check) where a location is set? That removes the Unicode trimming and most of the test matrix. parseStorageURI should stay lenient so recorded URIs can still be collected.
There was a problem hiding this comment.
Agreed, switched to an allowlist. Where a location is set (template create, ate-api-server's --snapshot-location and ate-setup deploy), each path segment now has to match ^[A-Za-z0-9_-]+$ and can't be atespaces in any case. Templates that are already stored keep the narrower atespaces check, so creating a tag from one with, say, a dotted location doesn't start failing. parseStorageURI, ParseStoragePrefix and SnapshotOwner.Prefix stay lenient, so recorded URIs still parse and get collected. The Unicode trimming and its test rows are gone.
0d9481d to
62bde8c
Compare
Greg Castle (destijl)
left a comment
There was a problem hiding this comment.
Thanks, this looks good to me. CI's controlapi build fails on the new TestTagActorSnapshot_StoredLocationOutsideSegmentRules: ateapi.SnapshotStorageStatus_SNAPSHOT_STORAGE_STATUS_COMPLETED no longer exists on main, so it needs a rebase and a test update.
| return nil, fmt.Errorf(`invalid snapshot location %q: must not contain '\'`, location) | ||
| } | ||
| for segment := range strings.SplitSeq(u.Path, "/") { | ||
| if segment != "" && strings.Trim(segment, ". ") == "" { |
There was a problem hiding this comment.
I think this is right as is. parseStorageURI is called on the garbage collection / reading path (ParseSnapshotURI, ParseStoragePrefix, SnapshotOwner.Prefix). If you tighten parseStorageURI to reject Unicode spaces or other non-ASCII characters, any historical snapshot created under a previously legal location that contained non-ASCII characters could never be deleted or collected by the GC workflow. The new restrictions make sure new bad paths can't be set.
|
Adding Dmitry Berkovich (@dberkov) somewhat at random for the "needs code freeze merge approval" decision. |
4e922e6 to
4514dd6
Compare
Snapshot objects live under <location>/atespaces/<atespace>/..., so a location that contains an atespaces segment could put one template's snapshots inside another actor's prefix, its grant and its cleanup. Where a location is set, each path segment may now contain only ASCII letters, digits, '_' and '-', and no segment may be atespaces in any letter case. Creating a tag checks the template's location against the same rules and fails with FAILED_PRECONDITION on a bad one. Signed-off-by: Anish Ramasekar <[email protected]>
4514dd6 to
4ae8693
Compare
|
Dmitry Berkovich (@dberkov) this is ready for approval and merge. |
Signed-off-by: Anish Ramasekar <[email protected]>
1ebfae7
|
Dmitry Berkovich (@dberkov) pushed changes to address your comments, PTAL! |
Signed-off-by: Anish Ramasekar <[email protected]>
|
Benjamin Elder (@BenTheElder) this merged after rc.1 was cut and isn't on release-1.0. It changes which snapshot storage paths are accepted (breaking-change), so I'd rather it ship in 1.0.0 than in a 1.0.x patch. Could it get a cherry-pick before the tag? Happy to open the cherry-pick PR if that's easier. |
Snapshots are stored under
`<location>/atespaces/<atespace>/{actors,tags}/<uid>`. A
`storageLocation` that already contained that layout (for example
`gs://snaps/root/atespaces/team-a/actors/<uid>`) put one template's
snapshots inside another actor's prefix, grant and cleanup.
Where a location is set (template create, `--snapshot-location`,
`ate-setup deploy`), each path segment may now contain only ASCII
letters, digits, `_` and `-`, and may not be `atespaces` in any letter
case. The bucket is not restricted.
- Creating a tag checks the template's location against the same rules
and returns `FAILED_PRECONDITION` on a bad one.
- Parsing of recorded snapshot URIs stays lenient, so stored snapshots
still resume and get collected.
Breaking change: if the path of your `--snapshot-location`
(`SNAPSHOT_LOCATION`) has a segment outside `^[A-Za-z0-9_-]+$` (a dot,
`%`, an empty segment, non-ASCII) or equal to `atespaces` in any case,
change it before upgrading. Otherwise ate-api-server refuses to start.
- [x] Tests pass (touched packages, `hack/verify-all.sh`)
- [x] Docs updated
AI use: I used Claude Code to implement and review this change, and
reviewed the diff myself.
---------
Signed-off-by: Anish Ramasekar <[email protected]>
(cherry picked from commit c53f4b2)
Signed-off-by: Eitan Yarmush <[email protected]>
Snapshots are stored under
<location>/atespaces/<atespace>/{actors,tags}/<uid>. AstorageLocationthat already contained that layout (for examplegs://snaps/root/atespaces/team-a/actors/<uid>) put one template's snapshots inside another actor's prefix, grant and cleanup.Where a location is set (template create,
--snapshot-location,ate-setup deploy), each path segment may now contain only ASCII letters, digits,_and-, and may not beatespacesin any letter case. The bucket is not restricted.FAILED_PRECONDITIONon a bad one.Breaking change: if the path of your
--snapshot-location(SNAPSHOT_LOCATION) has a segment outside^[A-Za-z0-9_-]+$(a dot,%, an empty segment, non-ASCII) or equal toatespacesin any case, change it before upgrading. Otherwise ate-api-server refuses to start.hack/verify-all.sh)AI use: I used Claude Code to implement and review this change, and reviewed the diff myself.