[KYUUBI #7593][FEATURE][AUTHZ] "Paranoid mode", try to catch missing authz checks - #7594
Open
acruise wants to merge 7 commits into
Open
[KYUUBI #7593][FEATURE][AUTHZ] "Paranoid mode", try to catch missing authz checks#7594acruise wants to merge 7 commits into
acruise wants to merge 7 commits into
Conversation
… fail-opens Rebase fallout: - AlterColumns (new in upstream Spark 4 port) gets explicit verifiedSparkVersions 4.0/4.1/4.2 - the pre-Spark-4 default baseline cannot apply to it - Regenerate 3.5 backlog: Hudi 1.2.0 renamed plans.logcal -> plans.logical Found by running the existing suites in deny mode on upstream master: - Iceberg metadata tables (t.snapshots etc) were never authorized: their 4-part name() blew up StringTableExtractor and the MatchError vanished into the fail-open path. TableTableExtractor now reflectively unwraps BaseMetadataTable to its base table, so metadata reads are authorized as base-table reads; new regression test. - Iceberg MERGE INTO embeds an already-planned DataSourceV2ScanRelation the builder skipped silently; classified with its own scan spec.
Full module suite passes in deny mode under -Pspark-4.1 -Pscala-2.13 (642 tests). New Spark 4 classifications, both caught by deny-mode runs: - SaveAsV1TableCommand (SPARK-49246): DataFrameWriter.saveAsTable v1 path analyzes into this leaf wrapper which only plans the real CTAS inside a nested QueryExecution at run time; spec'd directly (CREATETABLE_AS_SELECT) so the write is authorized on the outer plan instead of relying on the nested pass - ShowNamespacesCommand: Spark 4.x's v1 SHOW DATABASES; allowlisted with the same 'enforced elsewhere' rationale as v2 ShowNamespaces (upstream's port already routes it through ObjectFilterPlaceHolder row filtering), plus the required second exemption in ClassificationCoverageSuite Allowlist entries re-reviewed and extended to 4.0/4.1/4.2 (full-suite deny runs per profile are the verification vehicle); refreshed 4.1 backlog against upstream Spark 4.1.2 + Iceberg 1.11/Hudi 1.2/Delta 4.3/Paimon 1.4 (195 entries)
Full module suite passes in deny mode under -Pspark-4.0 -Pscala-2.13 (678 tests, first run - no new unclassified nodes beyond what 4.1 already surfaced).
…5-4.2 - Add 4.2 classification backlog (208 entries; connector jars stay on the 4.2 classpath even though their suites are tag-excluded there) - Extend verifiedSparkVersions now that the per-profile deny runs prove them: SaveAsV1TableCommand 4.0/4.1/4.2, DataSourceV2ScanRelation 3.5/4.0/4.1 (Iceberg is tag-excluded on 4.2, so no 4.2 claim) - Refresh backlog counts in the design doc (3.5=135, 4.0=182, 4.1=195, 4.2=208) Full module suite in deny mode: 3.5 677+1, 4.0 678, 4.1 642, 4.2 428 (CI tag exclusions) - all green.
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.
Fixes #7593.
Why are the changes needed?
Privilege building walks the logical plan and classifies each node: it has a command spec, a scan spec, or it is a pass-through. A node matching none of these is currently silent: the walk continues and the statement executes with whatever privileges the rest of the plan happened to require. Nothing logs at a level an operator would see, so an unauthorized plan shape is indistinguishable from an authorized one.
That silence is load-bearing in two ways. A plan node can change shape under the same fully qualified name between Spark releases (
CALLchanged supertype between Spark 3 and 4 and stopped matching its dispatch arm), and a third-party catalog can introduce nodes the spec set has never seen. In both cases authorization stops happening rather than fails.Running the existing suites with unclassified nodes set to
denysurfaced two live fail-opens on master. Both are fixed here with regression tests:SELECT * FROM db.t.snapshots(and.history,.manifests, ...) reports a four-partname().StringTableExtractormatches only one-, two- and three-part names, so a four-part name throwsMatchError;CommandSpeccatchescase e: Exception, logs atdebug, and returnsNone. The table therefore carries no access request and the read is not authorized at all. Iceberg metadata tables expose table history, manifest listings and file paths.TableTableExtractornow reflectively unwrapsBaseMetadataTableto its base table, so the read is authorized as a read of the table it exposes.MERGE INTOskipped its read of the target. The rewrite embeds an already-plannedDataSourceV2ScanRelationfor that read, which the plan builder passed over silently. It now has its own scan spec.TableTableExtractoris byte-identical inv1.10.3,v1.11.1and currentmaster, so both gaps are present in released versions, not just on master.The main change adds
spark.kyuubi.authz.unclassifiedNode.behavior(allow|warn|deny, defaultwarn) so an operator can choose to fail closed on an unclassified node, plus the build-time accounting that makes the classification set auditable rather than assumed: every concreteLogicalPlandescendant on the classpath is placed in exactly one of four buckets — spec'd, allowlisted, pass-through, or a per-Spark-minor backlog file that ischecked in and reviewed at PR time.
Two design points worth reviewer attention:
reason. An entry grants silence, so it should record a reviewed decision rather than a reflexive silencing.major.minorversions its review covers, as an explicit enumeration rather than a range. Ranges invite boundary misreadings ("less than 4.0,exclusive" read as inclusive); an enumeration has no boundary to misread and makes a new Spark minor unverified by default, which is exactly when re-review is due. Allowlist entries gate on this — unverified means inert, so the node counts as unclassified and fails closed. Command and scan specs are advisory, because a spec imposes checks and staying active on an unaudited version is the safe direction.
Specs added before the Spark 4 port take their versions from a frozen ledger (
spec_verified_spark_versions.txt) that records an inherited3.3/3.4/3.5baseline rather than a per-minor audit; the ledger header says so plainly and is closed, so a new spec must declare its own versions rather than inherit that baseline.docs/paranoid-mode.mdcarries the full design, including the known limitation thatRuleAuthorizationis an optimizer rule and so cannot stop anExecutableDuringAnalysisnode (Spark 4CALL) that has already run during analysis — which is what the build-time check exists to cover.Default behaviour is unchanged:
warnlogs and proceeds.How was this patch tested?
ParanoidModeSuite(15 tests) covers the policy: thewarndefault, case-insensitive parsing, an invalid value rejected loudly, and each behaviour end to end —denyfailing closed on an unclassified leaf relation and on an unclassified command,warnpassing but counting every occurrence,allowpassing silently. It also covers the shapes that made this worth doing: an unclassified node cannot hide under a constant projection, scan spec extraction failures are surfaced rather than swallowed, allowlisted nodes still pass underdeny, and ordinary multi-operator queries recurse freely. Allowlist hygiene is tested too — entries require a reason and explicitly enumerated versions rather than ranges, and an entry applies only to the Spark minors it was reviewed against.ClassificationCoverageSuite(5 tests) does the build-time accounting: every plan node class on the classpath is accounted for, every command spec entry is routable tobuildCommand, allowlisted classes are not Commands in disguise, allowlist entries are shapes the classifier would actually flag (so an entry for a pass-through shape is rejected as dead weight that reads as coverage), and no class has both a command spec and an allowlist entry.For the two fail-opens:
selecting an Iceberg metadata table requires select on the base table, asserting both directions — denied for an unprivileged user, allowed for one with select on the base table. It fails without theTableTableExtractorchange.MERGE INTOgap has no new test of its own; it is covered by the existing[KYUUBI #3515] MERGE INTOtest now running underdeny. Without the newDataSourceV2ScanRelationscan spec the embedded relation is unclassified, so that test fails. This holds on the 3.5, 4.0 and 4.1 profiles; Iceberg is tag-excluded on 4.2.The ledger's two guards — a spec that neither declares
verifiedSparkVersionsnor appears in the ledger, and a ledger entry whose spec no longer exists — are assertions inside the existingcheck spec json filesgenerator test rather than standalone tests. Both fail generation.Existing tests: all suites now run with sessions in
denymode (SparkSessionProvider), so any unclassified node reached by an existing test fails that test rather than passing silently. That is the main verification vehicle for the classification set, and it is how both fail-opens above were found.Results: the default profile passes on this branch (697 tests). The one failure in that run is unrelated to this change and reproduces on
master— Hudi's embedded timeline server timing out to a LAN address inDeleteHoodieTableCommand/UpdateHoodieTableCommand/MergeIntoHoodieTableCommand. The 4.0, 4.1and 4.2 profiles were green in
denymode before this branch was rebased onto current master and are being re-run against it; I will confirm here before asking for a merge.Was this patch authored or co-authored using generative AI tooling?
Assisted-by: Claude:claude-opus-5
Used for code review, rebasing onto master, and drafting; every line reviewed by me.