peter-toth commented on code in PR #58599:
URL: https://github.com/apache/spark/pull/58599#discussion_r3987187326


##########
resource-managers/yarn/src/main/scala/org/apache/spark/deploy/yarn/ApplicationMaster.scala:
##########
@@ -695,7 +695,20 @@ private[spark] class ApplicationMaster(
   /** Add the Yarn IP filter that is required for properly securing the UI. */
   private def addAmIpFilter(driver: Option[RpcEndpointRef], proxyBase: String) 
= {
     val amFilter = classOf[AmIpFilter].getName
-    val params = client.getAmIpFilterParams(yarnConf, proxyBase)
+    val baseParams = client.getAmIpFilterParams(yarnConf, proxyBase)
+    val trustProxyUserCookie = sparkConf.get(AM_TRUST_PROXY_USER_COOKIE)
+    // Refuse to arm the option silently: when the cookie is not trusted and 
no other UI filter
+    // will run to establish the user (cluster mode replaces spark.ui.filters 
with the AM's own
+    // filter; client mode may also have no other filter), proxied requests 
carry no user and pass
+    // every view and modify ACL check.
+    if (!trustProxyUserCookie && (driver.isEmpty || 
sparkConf.get(UI_FILTERS).isEmpty)) {

Review Comment:
   **Finding 6.** The condition tests whether another UI filter is 
*configured*. What matters is whether another filter *establishes a user*, and 
the config text draws exactly that distinction ("an authentication filter that 
wraps the request ... but a filter that authenticates without wrapping the 
request ...").
   
   An IP allowlist or a token check in `spark.ui.filters` makes 
`sparkConf.get(UI_FILTERS).isEmpty` false, so the warning stays silent. That is 
the configuration the doc recommends, and per finding 1 it is also the one 
where every ACL check starts passing. So the warning fires only for the case 
the doc already tells operators not to use, and is quiet for the case it tells 
them to use.
   
   No conf inspection can tell whether a filter wraps the request, so this 
condition cannot be made accurate. Warn whenever the option is off, and state 
the effect rather than the cause:
   
   ```scala
   if (!trustProxyUserCookie) {
     logWarning(log"${MDC(LogKeys.CONFIG, AM_TRUST_PROXY_USER_COOKIE.key)} is 
false, so proxied " +
       log"requests carry no user and pass every view and modify ACL check.")
   }
   ```
   



##########
resource-managers/yarn/src/main/scala/org/apache/spark/deploy/yarn/config/package.scala:
##########
@@ -301,6 +301,32 @@ package object config extends Logging {
     .intConf
     .createWithDefault(1)
 
+  private[spark] val AM_TRUST_PROXY_USER_COOKIE =
+    ConfigBuilder("spark.yarn.am.trustProxyUserCookie")
+      .doc("When true (default), the YARN AM UI filter uses the 'proxy-user' 
cookie set by the " +
+        "YARN RM web proxy to determine the user for the AM UI view/modify 
ACLs. This forwarded " +
+        "cookie is not cryptographically signed; fully guaranteeing its 
integrity would require " +
+        "the YARN RM web proxy to sign it (a Hadoop-side change), so this 
option is an interim " +
+        "workaround until then. The AM always installs its own filter first, 
so this option is " +
+        "meant for client mode, where a separate authentication filter set in 
spark.ui.filters " +
+        "runs after it. The last request wrapper in the chain determines the 
user: an " +
+        "authentication filter that wraps the request overrides the cookie 
principal on its own, " +
+        "so this option changes nothing there; but a filter that authenticates 
without wrapping " +
+        "the request (for example an IP or network allowlist, a token/header 
check that only " +
+        "accepts or rejects, or an SSO filter that passes whitelisted paths 
straight through) " +
+        "leaves the AM's own request wrapper outermost, so getRemoteUser() is 
still the " +
+        "unverified cookie value. That is the case this option is for: setting 
it to false makes " +

Review Comment:
   **Finding 1.** This is the sentence I asked for in round 1, and my round-1 
suggestion was wrong. I withdraw it. The non-wrapping-filter case is not where 
the option helps. It is where the option removes the ACL check.
   
   Once the cookie is ignored and nothing downstream wraps the request, 
`HttpSecurityFilter` reads `getRemoteUser() == null`, and 
`SecurityManager.isUserInACL` 
(`core/src/main/scala/org/apache/spark/SecurityManager.scala:406`) 
short-circuits to `true` on a null user:
   
   ```scala
   if (user == null ||
       !aclsEnabled() ||
       aclUsers.contains(WILDCARD_ACL) ||
       ...) {
     true
   }
   ```
   
   Measured against a `SecurityManager` built with `spark.acls.enable=true`, 
`spark.ui.view.acls=alice`, `spark.modify.acls=alice`, `spark.admin.acls=admin`:
   
   ```
   checkUIViewPermissions("mallory") = false   // forged cookie user, denied 
today
   checkUIViewPermissions(null)      = true    // the same request with this 
option off
   checkModifyPermissions(null)      = true    // kill job / kill stage
   checkAdminPermissions(null)       = true    // so ?doAs=<anyone> passes as 
well
   ```
   
   So for the three filter shapes this paragraph names, `false` does not stop a 
forged user from being honoured. It stops every user from being checked. 
`false` never denies anything that `true` allows, in any configuration. It 
either changes nothing (`spark.acls.enable=false`, or the cookie user was in 
the ACLs anyway) or it widens access.
   
   That means the next paragraph, "If this is set to `false` without such an 
authentication filter ... exposing the AM UI", describes the recommended 
configuration too, not just the discouraged one.
   
   Two ways out. The first is the alternative already offered in 
[issuecomment-5581070262](https://github.com/apache/spark/pull/58599#issuecomment-5581070262):
   
   - Fail closed. When the cookie is not trusted, `AmIpFilter` rejects the 
proxied request instead of forwarding it anonymously. This also cuts off the 
wrapping-auth-filter case, since the chain stops before that filter runs.
   - Keep it anonymous, and say plainly that `false` turns the AM UI view and 
modify ACLs off for proxied requests in every configuration where it has any 
effect. The config then has to stand on that, not on "the request reaches the 
ACL check with no forged user".
   
   Making the ACL layer itself fail closed on an unknown user would be the real 
fix, but that is a `SecurityManager` change well outside this PR.
   
   The same sentence is in `docs/running-on-yarn.md:267`.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to