Support compound app:userName rules in connector authorization#700
Conversation
Extend connector.authorized-callers to accept entries of the form `app:userName` in addition to plain `app` entries. Plain entries continue to match on SsoDirectCallerAppName only; compound entries require both SsoDirectCallerAppName AND the request userName to match exactly. This allows narrow grants like `hadoop:idm-worker` without broadening access to all hadoop-app callers. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
| if (!appName.equals(callerAppName)) { | ||
| return false; | ||
| } | ||
| if (userName == null) { |
There was a problem hiding this comment.
are there any edge cases where the username filed is not a null value but it's populated with a value that should be treated as null?
There was a problem hiding this comment.
example having "null" as user for example
There was a problem hiding this comment.
follow up, should an empty string be considered as null here?
There was a problem hiding this comment.
username here is from the catalog configuration like user in app:user. So this will always either be a value we set, or null if it's not colon separated like app2.
So we will only compare a string we set ie: user to the callerUserName which is in the request. So we are expecting the request to explicitly match the known string or we deny.
Summary
Extends connector authorization (
connector.authorized-callers) to support compound rules of the formapp:userName, in addition to the existing plainappentries.app1) — matches onSsoDirectCallerAppNameonly. Unchanged behavior.app2:user2) — requires bothSsoDirectCallerAppNameAND the requestuserNameto match exactly.Motivation
After #690, connector authorization checks
SsoDirectCallerAppNameagainst a flat allow list. This is too coarse for callers like:MetacatRequestContext{userName='user2', ..., additionalContext='{SsoDirectCallerAppName=app2, ...}'}
Allowing all of
app2would over-grant. We want a way to grant access only when both the direct caller app and the userName line up — e.g.app2+user2only — while leaving existing app-only entries working for other callers.Changes
AuthorizedCallervalue class (appNamerequired,userNamenullable) with amatches(callerApp, callerUserName)method.ConnectorFactoryDecoratornow parses each comma token, splitting on the first:to build compound rules. Empty tokens are skipped; matching remains case-sensitive.ConnectorAuthorizationUtil.checkAuthorizationreadsuserNamefrom the request context and usesanyMatchover the allow-list rules. Deny logs include the userName for easier debugging.Authorizing*Servicedecorators (Catalog,Database,Table,Partition) now holdSet<AuthorizedCaller>instead ofSet<String>.Config example
connector.authorization-required=true
connector.authorized-callers=app1,app2,app3:user1
Behavior with the above:
SsoDirectCallerAppNameuserNameapp1app3user1app3user2app3nullotheruser2Backwards compatibility
Configs with no
:in any entry parse to rules withuserName=nulland behave identically to the previous implementation. No deployment-time action needed for existing connectors.Test plan
./gradlew :metacat-common-server:test— all targeted specs passConnectorAuthorizationUtilSpec: compound match, userName mismatch, null userName, mixed plain+compound list, app mismatchConnectorFactoryDecoratorSpecforapp:userNamesyntaxAuthorizing*ServiceSpectests migrated toAuthorizedCallerand continue to pass