adapter: narrow ALTER CONNECTION authorization to dependent secrets - #1
Draft
jasonhernandez wants to merge 2 commits into
Draft
jasonhernandez wants to merge 2 commits into
jasonhernandez wants to merge 2 commits into
Conversation
`check_alter_connection_usage` seeds its worklist from `item_dependents(id)`, which includes `id` itself. Popping `id` expanded the altered connection's own `resolved_ids`, so the walk descended into the connections it references and collected their secrets. That reaches past the credentials the check exists to protect. Altering a Kafka connection that names an AWS connection now demanded USAGE on the AWS connection's secret, where USAGE on the AWS connection alone had been enough. It is the same delegation that lets `USAGE` on a fixed connection run `VALIDATE CONNECTION` without access to the secrets behind it, which `guide-adapter.md` states two paragraphs above the rule this added. Pre-visit `id` so the walk never descends through it. Its final definition is already in `usage_ids`, so a caller can still remove a dependency it cannot use, and dependent connections are still traversed in full. Also document the boundaries the check deliberately leaves alone: ROTATE KEYS does not point dependents at a new endpoint, and DROP ... CASCADE still needs no ownership of descendants. The user-facing remediation list no longer leads with granting the route owner USAGE on the secrets, which resolves the error by handing over the access the restriction exists to withhold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The AWS PrivateLink block repeats the SSH block statement for statement. The authorization walk never inspects a route's kind, so the transitive, inherited, ownership, and validation cases resolve through identical code either way. Keep the exhaustive sequence once, on SSH, and reduce PrivateLink to the denial, the unchanged catalog, and the success after the dependency is gone, which is what shows the path is reached at all. Net effect is 45 fewer lines in a file whose objects persist for the rest of the run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on MaterializeInc#38798 (targets
jc/issue-SQL-662, notmain). Two commits, bothdroppable independently. Net effect on the eventual
maindiff is -24 lines,while adding a regression test.
Motivation
I reviewed MaterializeInc#38798 from an RBAC angle. The rule it establishes is the right one:
redirecting a route should require a privilege that already lets you exfiltrate
the credentials flowing through it, so the check grants no new capability. Two
things looked worth changing before it lands.
1. The walk reaches past the credentials it protects
check_alter_connection_usageseeds fromitem_dependents(id), anditem_dependentsincludesiditself. Poppingidexpands the alteredconnection's own
resolved_ids, so the walk descends into the connections itreferences and harvests their secrets.
Concretely, on the current branch:
Before MaterializeInc#38798 that needed only
USAGEonaws_conn. It closes no part ofSQL-662 (nothing here is a dependent), and it contradicts the delegation rule
stated two paragraphs above the new text in
guide-adapter.md:USAGEon afixed connection runs
VALIDATE CONNECTIONwithout access to the secrets behindit. It also lands in an odd middle ground, requiring
USAGEon the leaf secretbut not on the intermediate connection holding it, since intermediates are
traversed and never added to
usage_ids.The fix pre-visits
idso the walk never descends through it. That also retiresthe
dependency_id == idsubstitution, since the altered connection's finaldefinition is already in
usage_ids. Removing an inaccessible dependency stillworks, and dependent connections are still traversed in full.
2. The user doc leads with the remediation that undoes the fix
The remediation list opened with "Grant the route administrator access to those
secrets." Under the availability pressure of a shared tunnel that cannot be
altered, that is the path of least resistance, and it ends with route admins
holding
USAGEon every team's secrets, which is the capability this changeexists to deny. Reordered so delegating the
ALTERcomes first and the broadgrant comes last with a caveat.
While in there, documented two boundaries the check deliberately leaves alone,
since both are load-bearing and neither was written down:
ROTATE KEYSstays ownership-only. It does not point dependents at a newendpoint, so it is correctly out of scope, but that carve-out is unpinned.
DROP ... CASCADEstill needs no ownership of descendants(
rbac.rs,Plan::DropObjects). A route owner cannot redirect a dependent'scredentials but can still destroy the dependent. That is defensible, the
boundary being confidentiality rather than availability, but the user doc now
tells people to "remove the dependency", which is exactly that power.
Verification
test/sqllogictest/privilege_checks.sltgains a case for the delegation path:joe owns
ssh_route_kafka, holdsUSAGEonssh_route_aws, and cannot use thesecret behind it. Confirmed red before the fix and green after:
The second failure in that reverted run is
alter_conn_secret, the pre-existingretained-dependency case from
main, which is a useful check that the fix keepsthat property rather than trading one for the other.
The second commit collapses the AWS PrivateLink block, which repeats the SSH
block statement for statement. The walk never inspects a route's kind, so both
resolve through identical code. PrivateLink keeps the denial, the unchanged
catalog, and the success after the dependency is gone. Drop this commit if you
would rather keep the symmetry with the doc, which names both route types.
bin/fmt,cargo clippy -p mz-adapter --all-targets -D warnings, andbin/lintare clean apart from the pre-existing
zizmorandcheck-helm-docsfailures youalready flagged.
Left for you
Not addressed here, listed so they are not lost:
stage_finishre-check is the TOCTOU defense and nothing exercises it. WithVALIDATE = truethe request already fails atstage_ready, so showing thewindow needs a Rust integration test that revokes during validation.
item_dependentswalks the entire downstream graph, every source, table, view,MV and index, then discards all non-connections, twice per
ALTER. Aused_byBFS restricted to connections is equivalent, since nothing but a connection can
be
used_bya connection, and it would share code with the walk incatalog_implications.rs::handle_alter_connection. Those two walks have toagree or a redirect propagates somewhere it was never authorized. I left this
alone to keep the diff small.
USAGEon, possiblyin a schema they cannot see. Naming the dependent connection instead would say
as much without the enumeration oracle.
secrets is a real deployment shape, and this is the first rule where an action
on your own object is blocked by another role's object.
🤖 Generated with Claude Code