dongjoon-hyun commented on code in PR #59185:
URL: https://github.com/apache/spark/pull/59185#discussion_r4158581876


##########
core/src/main/scala/org/apache/spark/deploy/security/UserCredentialManager.scala:
##########
@@ -519,6 +527,44 @@ private[spark] object UserCredentialManager extends 
Logging {
     }
   }
 
+  /**
+   * Returns true if Spark's RPC channel encryption is configured: either 
SSL-based RPC encryption
+   * (`spark.ssl.rpc.enabled`), or authentication (`spark.authenticate`) 
together with either
+   * AES-based encryption (`spark.network.crypto.enabled`) or the separate 
(deprecated) SASL-based
+   * encryption (`spark.authenticate.enableSaslEncryption`).
+   *
+   * This is shared with [[HadoopDelegationTokenManager]]'s 
direct-credential-provider check so the
+   * two credential-propagation paths cannot drift apart: that path fails 
startup via `require()`,
+   * while OIDC propagation only warns (see [[warnIfRpcEncryptionDisabled]]).
+   */
+  private[security] def isRpcEncryptionEnabled(sparkConf: SparkConf): Boolean 
= {
+    sparkConf.getBoolean("spark.ssl.rpc.enabled", false) ||
+      (sparkConf.get(NETWORK_AUTH_ENABLED) &&
+        (sparkConf.get(NETWORK_CRYPTO_ENABLED) || 
sparkConf.get(SASL_ENCRYPTION_ENABLED)))
+  }
+
+  /**
+   * Logs a warning if OIDC credential propagation is enabled but RPC channel 
encryption is not
+   * configured, since credentials would then be transmitted over the RPC 
channel in cleartext.
+   * Unlike [[HadoopDelegationTokenManager]]'s direct-credential-provider path 
(which fails fast),
+   * this only warns, per the SPIP's default behavior.
+   */
+  private[security] def warnIfRpcEncryptionDisabled(sparkConf: SparkConf): 
Unit = {
+    if (!isRpcEncryptionEnabled(sparkConf)) {
+      // Reference config keys via `.key` (rather than hardcoded strings) so 
the message stays in
+      // sync if a key is renamed. `spark.ssl.rpc.enabled` has no Scala 
ConfigEntry (it is read
+      // directly in TransportConf), so it is spelled out literally here and in
+      // isRpcEncryptionEnabled.
+      val remediation = s"Enable RPC encryption via 
spark.ssl.rpc.enabled=true, or " +

Review Comment:
   Two small things on the message construction:
   
   - `s"Enable RPC encryption via spark.ssl.rpc.enabled=true, or "` has no 
interpolated value, so the `s` prefix is not needed.
   - The comment above says keys are referenced via `.key` to stay in sync, but 
`spark.ssl.rpc.enabled` is spelled out here and again in 
`isRpcEncryptionEnabled`. A single private constant used by both would keep 
them in sync and make the explanatory comment unnecessary.



##########
core/src/main/scala/org/apache/spark/deploy/security/UserCredentialManager.scala:
##########
@@ -519,6 +527,44 @@ private[spark] object UserCredentialManager extends 
Logging {
     }
   }
 
+  /**
+   * Returns true if Spark's RPC channel encryption is configured: either 
SSL-based RPC encryption
+   * (`spark.ssl.rpc.enabled`), or authentication (`spark.authenticate`) 
together with either
+   * AES-based encryption (`spark.network.crypto.enabled`) or the separate 
(deprecated) SASL-based
+   * encryption (`spark.authenticate.enableSaslEncryption`).
+   *
+   * This is shared with [[HadoopDelegationTokenManager]]'s 
direct-credential-provider check so the
+   * two credential-propagation paths cannot drift apart: that path fails 
startup via `require()`,
+   * while OIDC propagation only warns (see [[warnIfRpcEncryptionDisabled]]).
+   */
+  private[security] def isRpcEncryptionEnabled(sparkConf: SparkConf): Boolean 
= {

Review Comment:
   This is now a third copy of the "is RPC encrypted" decision. 
`SecurityManager.isEncryptionEnabled()` and `TransportConf` already read 
`spark.ssl.rpc.enabled` on their own, and this PR also makes 
`HadoopDelegationTokenManager` (the Kerberos / direct-provider path) depend on 
the OIDC-specific `UserCredentialManager` object just to reach the predicate.
   
   If the rule changes later (for example a new transport-encryption option), 
these copies can drift apart, and a missed warning here means credentials go 
out in cleartext with no signal. Could the predicate live somewhere neutral 
(e.g. `SecurityManager` or a util object), or reuse the existing check, so both 
credential paths depend on that rather than on each other?



##########
core/src/main/scala/org/apache/spark/deploy/security/UserCredentialManager.scala:
##########
@@ -519,6 +527,44 @@ private[spark] object UserCredentialManager extends 
Logging {
     }
   }
 
+  /**
+   * Returns true if Spark's RPC channel encryption is configured: either 
SSL-based RPC encryption
+   * (`spark.ssl.rpc.enabled`), or authentication (`spark.authenticate`) 
together with either
+   * AES-based encryption (`spark.network.crypto.enabled`) or the separate 
(deprecated) SASL-based
+   * encryption (`spark.authenticate.enableSaslEncryption`).
+   *
+   * This is shared with [[HadoopDelegationTokenManager]]'s 
direct-credential-provider check so the
+   * two credential-propagation paths cannot drift apart: that path fails 
startup via `require()`,
+   * while OIDC propagation only warns (see [[warnIfRpcEncryptionDisabled]]).
+   */
+  private[security] def isRpcEncryptionEnabled(sparkConf: SparkConf): Boolean 
= {
+    sparkConf.getBoolean("spark.ssl.rpc.enabled", false) ||
+      (sparkConf.get(NETWORK_AUTH_ENABLED) &&
+        (sparkConf.get(NETWORK_CRYPTO_ENABLED) || 
sparkConf.get(SASL_ENCRYPTION_ENABLED)))
+  }
+
+  /**
+   * Logs a warning if OIDC credential propagation is enabled but RPC channel 
encryption is not
+   * configured, since credentials would then be transmitted over the RPC 
channel in cleartext.
+   * Unlike [[HadoopDelegationTokenManager]]'s direct-credential-provider path 
(which fails fast),
+   * this only warns, per the SPIP's default behavior.
+   */
+  private[security] def warnIfRpcEncryptionDisabled(sparkConf: SparkConf): 
Unit = {
+    if (!isRpcEncryptionEnabled(sparkConf)) {
+      // Reference config keys via `.key` (rather than hardcoded strings) so 
the message stays in
+      // sync if a key is renamed. `spark.ssl.rpc.enabled` has no Scala 
ConfigEntry (it is read
+      // directly in TransportConf), so it is spelled out literally here and in
+      // isRpcEncryptionEnabled.
+      val remediation = s"Enable RPC encryption via 
spark.ssl.rpc.enabled=true, or " +
+        s"${NETWORK_AUTH_ENABLED.key}=true together with one of " +
+        s"${NETWORK_CRYPTO_ENABLED.key} / ${SASL_ENCRYPTION_ENABLED.key}."
+      logWarning(log"OIDC credential propagation is enabled " +
+        log"(${MDC(LogKeys.CONFIG, SECURITY_OIDC_ENABLED.key)}=true) but RPC 
channel encryption " +
+        log"is not configured. Credentials will be transmitted to executors 
over an unencrypted " +
+        log"channel. ${MDC(LogKeys.REASON, remediation)}")

Review Comment:
   `LogKeys.REASON` is used elsewhere for the reason an operation failed. Here 
it carries a multi-sentence remediation hint, so with structured logging the 
`reason` field for this warning contains config-key advice, which will be mixed 
in with real failure reasons when filtering or aggregating. Plain `log"..."` 
text for the hint (or separate MDC values for the keys) would avoid that.
   
   Also, the message says credentials "will be transmitted ... over an 
unencrypted channel", but Spark cannot know that when transport is protected 
outside Spark (service mesh mTLS, private network), and there is currently no 
way to silence the warning in that case. Wording such as "may be transmitted" 
would be more accurate, and it could point at the stricter mode mentioned in 
the SPIP.



##########
core/src/main/scala/org/apache/spark/deploy/security/HadoopDelegationTokenManager.scala:
##########
@@ -79,10 +78,7 @@ private[spark] class HadoopDelegationTokenManager(
     "Both principal and keytab must be defined, or neither.")
 
   if (sparkConf.get(DIRECT_CREDENTIAL_PROVIDERS_ENABLED)) {
-    val hasEncryption = sparkConf.getBoolean("spark.ssl.rpc.enabled", false) ||
-      (sparkConf.get(NETWORK_AUTH_ENABLED) &&
-        (sparkConf.get(NETWORK_CRYPTO_ENABLED) || 
sparkConf.get(SASL_ENCRYPTION_ENABLED)))
-    require(hasEncryption,
+    require(UserCredentialManager.isRpcEncryptionEnabled(sparkConf),

Review Comment:
   The predicate moved, but the error message right below still lists the key 
combinations in its own wording, and the OIDC warning builds its text 
separately again. If `isRpcEncryptionEnabled` is extended later, the 
`require()` message and the warning text no longer match what the check 
accepts. Building both messages from one place next to the predicate would 
avoid that.



##########
core/src/test/scala/org/apache/spark/deploy/security/UserCredentialManagerSuite.scala:
##########
@@ -858,4 +859,90 @@ class UserCredentialManagerSuite extends SparkFunSuite {
       AnotherFakeCredentialProvider.throwOnProperties = false
     }
   }
+
+  // ========== RPC encryption warning ==========
+
+  private val encryptionWarningFragment = "RPC channel encryption is not 
configured"

Review Comment:
   The log-based tests match on this exact fragment, which spans two 
concatenated `log""` pieces in the main code, so rewording the message breaks 
all three tests even though behavior is unchanged. Consider asserting on a 
stable part (for example the OIDC config key, or the log level plus a shorter 
fragment).
   
   The `isRpcEncryptionEnabled` truth table also omits a few combinations: 
`spark.ssl.rpc.enabled` together with auth + crypto, and 
`spark.ssl.rpc.enabled` combined with only crypto. And since 
`HadoopDelegationTokenManager`'s `require()` now goes through the shared 
predicate, a small test that it still rejects/accepts the same configs would 
guard the refactor.



-- 
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