[
https://issues.apache.org/jira/browse/HADOOP-19971?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18134028#comment-18134028
]
ASF GitHub Bot commented on HADOOP-19971:
-----------------------------------------
joseluisll commented on code in PR #8703:
URL: https://github.com/apache/hadoop/pull/8703#discussion_r4409593247
##########
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/ServletSecretProviderContext.java:
##########
@@ -0,0 +1,117 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.hadoop.security.authentication.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletContext;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.classification.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Presents a ServletContext to a {@link SignerSecretProvider} as a
+ * {@link SecretProviderContext}.
+ * <p>
+ * Reads and writes go straight through to the ServletContext, so an object a
+ * provider shares this way - the CuratorFramework client of
+ * {@link ZKSignerSecretProvider} - is still a ServletContext attribute under
+ * the same name, and is still found there by everything that looks for it,
+ * including DelegationTokenAuthenticationFilter in hadoop-common.
+ * <p>
+ * This class is the one place in the provider hierarchy that names a servlet
+ * type, and it goes away with the deprecated
+ * {@link SignerSecretProvider#init(java.util.Properties, ServletContext,
long)}
+ * it exists to support.
+ */
[email protected]
[email protected]
+final class ServletSecretProviderContext implements SecretProviderContext {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(ServletSecretProviderContext.class);
+
+ private final ServletContext servletContext;
+
+ private ServletSecretProviderContext(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ /**
+ * Returns a context backed by the given ServletContext, or one backed by a
+ * map of its own when there is no ServletContext.
+ * <p>
+ * Most callers of the deprecated init pass null - only AuthenticationFilter
+ * ever passes a real context - and a provider that does not use the store is
+ * unaffected either way. Handing back a store rather than null keeps a
+ * provider that does use it from having to check.
+ *
+ * @param servletContext the servlet context, or null
+ * @return an attribute store, never null
+ */
+ static SecretProviderContext of(ServletContext servletContext) {
+ return servletContext == null
+ ? new MapSecretProviderContext()
Review Comment:
Agreed. A null ServletContext now fails on the first
`getAttribute`/`setAttribute` with an NPE that names the attribute. That is the
same exception and the same point of failure as on trunk, where
`ZKSignerSecretProvider.init` failed at `servletContext.getAttribute`. The map
store is removed.
##########
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/SignerSecretProvider.java:
##########
@@ -24,20 +24,77 @@
* do more complicated things in the backend.
* See the RolloverSignerSecretProvider class for an implementation that
* supports rolling over the secret at a regular interval.
+ * <p>
+ * Implementations should override
+ * {@link #initialize(Properties, SecretProviderContext, long)}, which names no
+ * servlet type. The older {@link #init(Properties, ServletContext, long)} is
+ * still the entry point callers use and is still honoured when an
+ * implementation overrides it, so providers written against it keep working
+ * unchanged; it is deprecated and will be removed with the move to the jakarta
+ * servlet namespace.
+ * <p>
+ * One consequence for code that reflects over these classes: the providers
+ * shipped here override
+ * {@link #initialize(Properties, SecretProviderContext, long)} rather than
+ * {@link #init(Properties, ServletContext, long)}, so init is no longer among
+ * their declared methods. Calls and overrides are unaffected - init is
+ * inherited from this class and resolves as it always did - but
+ * Class#getDeclaredMethod("init", ...) on a subclass such as
+ * {@link FileSignerSecretProvider} now raises NoSuchMethodException where it
+ * used to succeed. Class#getMethod, which searches superclasses, still finds
+ * it.
*/
@InterfaceStability.Unstable
@InterfaceAudience.Private
public abstract class SignerSecretProvider {
/**
- * Initialize the SignerSecretProvider
+ * Initialize the SignerSecretProvider.
+ * <p>
+ * The default implementation adapts the ServletContext to a
+ * {@link SecretProviderContext} and calls
+ * {@link #initialize(Properties, SecretProviderContext, long)}, so a
provider
+ * that overrides only that method is initialized correctly through this
+ * entry point. A provider that overrides this method instead is called
+ * directly, as before.
+ *
* @param config configuration properties
* @param servletContext servlet context
* @param tokenValidity The amount of time a token is valid for
* @throws Exception thrown if an error occurred
+ * @deprecated override
+ * {@link #initialize(Properties, SecretProviderContext, long)},
+ * which does not name a servlet type and so is unaffected by
+ * which servlet namespace the container provides.
*/
- public abstract void init(Properties config, ServletContext servletContext,
- long tokenValidity) throws Exception;
+ @Deprecated
+ public void init(Properties config, ServletContext servletContext,
+ long tokenValidity) throws Exception {
+ initialize(config, ServletSecretProviderContext.of(servletContext),
+ tokenValidity);
+ }
+
+ /**
+ * Initialize the SignerSecretProvider against an attribute store.
+ * <p>
+ * The default implementation throws, because a provider has to implement one
+ * of the two initialization methods. It is never reached by a provider that
+ * overrides the deprecated
+ * {@link #init(Properties, ServletContext, long)}, since callers go through
+ * that method and its override does not delegate here.
+ *
+ * @param config configuration properties
+ * @param context the attribute store to initialize against
+ * @param tokenValidity The amount of time a token is valid for
+ * @throws Exception thrown if an error occurred
+ */
+ public void initialize(Properties config, SecretProviderContext context,
Review Comment:
The "implements neither" case is covered by
`TestSignerSecretProviderCompatibility#testProviderImplementingNeitherMethodFailsLoudly`,
which checks that the message names the provider class. The `initialize`
javadoc says the default throws.
##########
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/SignerSecretProvider.java:
##########
@@ -24,20 +24,77 @@
* do more complicated things in the backend.
* See the RolloverSignerSecretProvider class for an implementation that
* supports rolling over the secret at a regular interval.
+ * <p>
+ * Implementations should override
+ * {@link #initialize(Properties, SecretProviderContext, long)}, which names no
+ * servlet type. The older {@link #init(Properties, ServletContext, long)} is
+ * still the entry point callers use and is still honoured when an
+ * implementation overrides it, so providers written against it keep working
+ * unchanged; it is deprecated and will be removed with the move to the jakarta
+ * servlet namespace.
+ * <p>
+ * One consequence for code that reflects over these classes: the providers
+ * shipped here override
+ * {@link #initialize(Properties, SecretProviderContext, long)} rather than
+ * {@link #init(Properties, ServletContext, long)}, so init is no longer among
+ * their declared methods. Calls and overrides are unaffected - init is
+ * inherited from this class and resolves as it always did - but
+ * Class#getDeclaredMethod("init", ...) on a subclass such as
+ * {@link FileSignerSecretProvider} now raises NoSuchMethodException where it
+ * used to succeed. Class#getMethod, which searches superclasses, still finds
+ * it.
*/
@InterfaceStability.Unstable
@InterfaceAudience.Private
public abstract class SignerSecretProvider {
/**
- * Initialize the SignerSecretProvider
+ * Initialize the SignerSecretProvider.
+ * <p>
+ * The default implementation adapts the ServletContext to a
+ * {@link SecretProviderContext} and calls
+ * {@link #initialize(Properties, SecretProviderContext, long)}, so a
provider
+ * that overrides only that method is initialized correctly through this
+ * entry point. A provider that overrides this method instead is called
+ * directly, as before.
+ *
* @param config configuration properties
* @param servletContext servlet context
* @param tokenValidity The amount of time a token is valid for
* @throws Exception thrown if an error occurred
+ * @deprecated override
+ * {@link #initialize(Properties, SecretProviderContext, long)},
+ * which does not name a servlet type and so is unaffected by
+ * which servlet namespace the container provides.
*/
- public abstract void init(Properties config, ServletContext servletContext,
- long tokenValidity) throws Exception;
+ @Deprecated
+ public void init(Properties config, ServletContext servletContext,
Review Comment:
`AuthenticationFilter` can't call `initialize` directly. A provider that
overrides only `init` would then fall through to the default `initialize` and
throw. That makes `init` the entry point every caller has to use until the
namespace change, so I've removed the deprecation. The class javadoc still
tells implementors to override `initialize`.
##########
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/CertificateUtil.java:
##########
@@ -38,8 +38,37 @@ public class CertificateUtil {
* - the pem encoding from config without the header and footer
* @return RSAPublicKey the RSA public key
* @throws ServletException thrown if a processing error occurred
+ * @deprecated use {@link #toRSAPublicKey(String)}, which reports the
+ * {@link CertificateException} it is really raising instead of
+ * wrapping it in a servlet type. This method is kept so that
+ * existing callers still compile and behave as before, and will
+ * be removed with the move to the jakarta servlet namespace.
*/
- public static RSAPublicKey parseRSAPublicKey(String pem) throws
ServletException {
+ @Deprecated
+ public static RSAPublicKey parseRSAPublicKey(String pem)
+ throws ServletException {
+ try {
+ return toRSAPublicKey(pem);
+ } catch (CertificateException ce) {
+ // Report the exception toRSAPublicKey wrapped, not the wrapper, so that
+ // the cause a caller sees is the one this method has always reported:
+ // the CertificateException the parse itself raised. Wrapping the wrapper
+ // would add a level to the chain that was not there before.
+ Throwable cause = ce.getCause() == null ? ce : ce.getCause();
Review Comment:
Agreed, the null-cause branch can't run. Both sites now pass `ce.getCause()`.
A package-private helper can't serve both sites: `CertificateUtil` is in
`...authentication.util` and the handler is in `...authentication.server`. The
helper would have to be a public method that names `ServletException`, on the
class this change moves off the servlet API. A test pins the chain at each site
(`TestCertificateUtil#testDeprecatedEntryPointDoesNotDeepenTheCauseChain`,
`TestJWTRedirectAuthenticationHandler#testCorruptPublicKeyPEMCauseChain`), so
if the two drift apart, a test fails.
##########
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/JWTRedirectAuthenticationHandler.java:
##########
@@ -125,7 +126,15 @@ public void init(Properties config) throws
ServletException {
throw new ServletException(
"Public key for signature validation must be provisioned.");
}
- publicKey = CertificateUtil.parseRSAPublicKey(pemPublicKey);
+ try {
+ publicKey = CertificateUtil.toRSAPublicKey(pemPublicKey);
+ } catch (CertificateException ce) {
+ // Report the exception toRSAPublicKey wrapped, not the wrapper, as
+ // CertificateUtil#parseRSAPublicKey does: the cause of this
+ // ServletException stays the CertificateException the parse raised.
+ Throwable cause = ce.getCause() == null ? ce : ce.getCause();
Review Comment:
See the `CertificateUtil` thread.
##########
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/SecretProviderContext.java:
##########
@@ -0,0 +1,52 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.hadoop.security.authentication.util;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * The attribute store a {@link SignerSecretProvider} is initialized against.
+ * <p>
+ * A SignerSecretProvider that has to share an object with the rest of the
+ * application it is embedded in - as {@link ZKSignerSecretProvider} shares its
+ * CuratorFramework client - reads and writes that object here. When the
+ * provider is running inside a servlet container the attributes are those of
+ * the ServletContext, so the sharing is unchanged; see
+ * {@link
org.apache.hadoop.security.authentication.server.AuthenticationFilter}.
+ * <p>
+ * This interface exists so that a SignerSecretProvider need not name a servlet
+ * type. Implementations of it that are backed by a ServletContext do, but the
+ * providers themselves stay independent of the servlet API and so of which
+ * servlet namespace - javax or jakarta - the container provides.
+ */
[email protected]
[email protected]
Review Comment:
`SignerSecretProvider` is `@Private`/`@Unstable` on trunk, and
`SecretProviderContext` has the same annotations as the class it is passed to.
The `@deprecated` note on `init` has been removed (see the `init` thread), so
there is no longer a deprecation note promising more than the annotations.
> Remove servlet and Jetty types from classes that do not need them
> -----------------------------------------------------------------
>
> Key: HADOOP-19971
> URL: https://issues.apache.org/jira/browse/HADOOP-19971
> Project: Hadoop Common
> Issue Type: Sub-task
> Components: auth, common
> Reporter: Jose Luis López
> Assignee: Jose Luis López
> Priority: Minor
> Labels: pull-request-available
>
> h3. Why
> Hadoop will eventually move from the javax.servlet namespace to
> jakarta.servlet. When it does, every class that mentions a servlet type has
> to be revisited, and every class that mentions a Jetty type has to be
> revisited again when the Jetty API changes underneath it.
> Several of those classes have no real connection to either. A component that
> generates signing secrets was handed the web server's context even though it
> only wanted somewhere to store an object. A certificate parser reported a bad
> certificate as a web server error. A Netty-based shuffle handler borrowed two
> header names from Jetty, and the YARN services client borrowed Jetty's URL
> encoder.
> This change removes those references, so those classes drop out of the later
> migration entirely.
> h3. What changes for anyone using Hadoop
> Nothing is removed, no signature changes shape, and no dependency, version or
> setting changes. Code built against today's release keeps compiling and
> running without being rebuilt.
> * Two entry points get a servlet-free alternative. The old ones stay, marked
> deprecated, until the namespace change:
> ** {{SignerSecretProvider.init(Properties, ServletContext, long)}} ->
> {{initialize(Properties, SecretProviderContext, long)}}
> ** {{CertificateUtil.parseRSAPublicKey(String)}} -> {{toRSAPublicKey(String)}}
> * Providers that override {{init}} are still called directly. A provider that
> extends Hadoop's rolling secret provider and calls {{super.init}} still gets
> its rollover scheduler started. {{TestSignerSecretProviderCompatibility}}
> pins this contract.
> * Exceptions are unchanged: same type, message and cause chain from
> {{parseRSAPublicKey}} and from {{JWTRedirectAuthenticationHandler}} when the
> PEM is corrupt.
> * The shuffle response is unchanged, byte for byte: the same {{Connection}}
> and {{Keep-Alive}} header names, capitalised as before.
> * The YARN services client now encodes {{user.name}} with
> {{java.net.URLEncoder}}. That differs from Jetty's encoder for two characters
> only: {{*}} is now sent as is where it used to be escaped, and {{~}} is now
> escaped where it used to be sent as is. Every code point decodes to the same
> value either way (checked across all of Unicode against Jetty 9.4.58), so the
> server reads the same user name.
> Things a downstream project could notice:
> * Deprecation warnings where code overrides {{init}} or calls
> {{parseRSAPublicKey}}.
> * {{getDeclaredMethod("init", ...)}} on the shipped providers now throws
> {{NoSuchMethodException}}, because they declare {{initialize}} instead.
> {{getMethod}} still finds {{init}}.
> * {{ZKSignerSecretProvider}} given a null ServletContext used to throw a
> NullPointerException. It now keeps its ZooKeeper client in a store private to
> itself and logs a WARN. Every caller in Hadoop passes a real context, and
> there the client is still stored as the ServletContext attribute of the same
> name.
> h3. What changes inside
> || || before || after ||
> | modules using Jetty in main sources | 12 | 11 |
> | main-source files using Jetty | 26 | 24 |
> | hadoop-auth main classes naming a servlet | 14 | 11 |
> Counted on trunk at a7c2bea723d.
> The MapReduce shuffle module no longer uses Jetty at all. In hadoop-auth, the
> secret providers and the authentication token no longer mention the servlet
> API. They are initialised against a small two-method store,
> {{SecretProviderContext}}, which holds whatever needs sharing; that is all
> the one provider that used the servlet context ever did with it. One new
> package-private class mentions the servlet API, and it exists only to keep
> the old path working.
> h3. What this does not do
> No namespace change, no Jetty upgrade, no Jersey change, no EE environments.
> The tree stays on Jetty 9.4, Jersey 2 and javax.servlet.
> The authentication handler classes still mention the servlet API,
> deliberately. They are the extension point that HBase, Hive, Spark, Ozone and
> Knox build against, and changing it is exactly the kind of break this issue
> is meant to avoid. That belongs with the namespace change, where downstream
> projects will expect it.
> h3. Ordering
> This is preparation for the jakarta move. It depends on nothing, is based on
> trunk, and nothing in HADOOP-19972 depends on it. The plan is to land it with
> or just before the namespace change in HADOOP-19395, in the next major
> release.
> When rebasing onto a later trunk:
> * If HADOOP-19970 has landed, it declares jetty-http in the shuffle module's
> pom only because of the import this change removes. Drop that declaration
> here.
> * If HADOOP-19972 has landed, re-check the shuffle headers and the
> {{user.name}} encoding against whatever Jetty classes it left in those two
> places.
> 16 files changed. Works on Jetty 9.4 and on the Jetty 12 ee8 environment of
> HADOOP-19912, and commits the namespace change in HADOOP-19395 to neither
> ee10 nor ee11.
> Contains content generated by Claude.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]