From c6f9d6946f3210e26315b46c5d6b743b8aec788d Mon Sep 17 00:00:00 2001 From: Hamid Date: Wed, 15 Apr 2026 16:44:15 +0200 Subject: [PATCH 1/2] bump hopsfs to 3.4.3.0-EE-RC1 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index b3930cb..3ad2798 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.hops.hadoop hadoop-apache - 3.4.3.0-EE-RC0 + 3.4.3.0-EE-RC1 hadoop-apache Shaded version of Apache Hadoop for Trino @@ -54,8 +54,8 @@ UTF-8 21 io.trino.hadoop.\$internal - 2.0.17 - 3.4.3.0-EE-RC0 + 1.7.36 + 3.4.3.0-EE-RC1 From 238ed6261a3a4664205361008398f83cdab7a95c Mon Sep 17 00:00:00 2001 From: Salman Niazi Date: Thu, 16 Apr 2026 15:21:08 +0200 Subject: [PATCH 2/2] Update Hops Hadoop Files based on 3.4.3.0-EE-RC1 --- pom.xml | 4 + .../crypto/key/kms/KMSClientProvider.java | 26 ++++ .../java/org/apache/hadoop/fs/FileSystem.java | 4 +- .../hadoop/security/UserGroupInformation.java | 117 +++++++++++++++--- 4 files changed, 129 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 3ad2798..12a854e 100644 --- a/pom.xml +++ b/pom.xml @@ -663,6 +663,10 @@ ${project.build.targetJdk} ${project.build.targetJdk} + + --add-exports + java.base/sun.security.x509=ALL-UNNAMED + diff --git a/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java b/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java index a918601..9e07b18 100644 --- a/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java +++ b/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java @@ -35,6 +35,7 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authentication.client.ConnectionConfigurator; import org.apache.hadoop.security.ssl.SSLFactory; +import io.hops.security.HopsFileBasedKeyStoresFactory; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.hadoop.security.token.TokenRenewer; @@ -387,6 +388,10 @@ public KMSClientProvider(URI uri, Configuration conf) throws IOException { canonicalService = SecurityUtil.buildTokenService(serviceUri); if ("https".equalsIgnoreCase(kmsUrl.getProtocol())) { + if (conf.getBoolean(CommonConfigurationKeysPublic.IPC_SERVER_SSL_ENABLED, + CommonConfigurationKeysPublic.IPC_SERVER_SSL_ENABLED_DEFAULT)) { + conf.set(SSLFactory.KEYSTORES_FACTORY_CLASS_KEY, HopsFileBasedKeyStoresFactory.class.getCanonicalName()); + } sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf); try { sslFactory.init(); @@ -1135,6 +1140,18 @@ private DelegationTokenAuthenticatedURL.Token generateDelegationToken( return token; } + private boolean containsKmsDt(UserGroupInformation ugi) throws IOException { + // Add existing credentials from the UGI, since provider is cached. + Credentials creds = ugi.getCredentials(); + if (!creds.getAllTokens().isEmpty()) { + LOG.debug("Searching for KMS delegation token in user {}'s credentials", + ugi); + return clientTokenProvider.selectDelegationToken(creds) != null; + } + + return false; + } + @VisibleForTesting UserGroupInformation getActualUgi() throws IOException { final UserGroupInformation currentUgi = UserGroupInformation @@ -1148,6 +1165,15 @@ UserGroupInformation getActualUgi() throws IOException { // Use real user for proxy user actualUgi = currentUgi.getRealUser(); } + if (UserGroupInformation.isSecurityEnabled() && + !containsKmsDt(actualUgi) && !actualUgi.shouldRelogin()) { + // Use login user is only necessary when Kerberos is enabled + // but the actual user does not have either + // Kerberos credential or KMS delegation token for KMS operations + LOG.debug("Using loginUser when Kerberos is enabled but the actual user" + + " does not have either KMS Delegation Token or Kerberos Credentials"); + actualUgi = UserGroupInformation.getLoginUser(); + } return actualUgi; } diff --git a/src/main/java/org/apache/hadoop/fs/FileSystem.java b/src/main/java/org/apache/hadoop/fs/FileSystem.java index 3e9c5cf..8fb0f17 100644 --- a/src/main/java/org/apache/hadoop/fs/FileSystem.java +++ b/src/main/java/org/apache/hadoop/fs/FileSystem.java @@ -264,7 +264,7 @@ static int cacheSize() { * @param user to perform the get as * @return the filesystem instance * @throws IOException failure to load - * @throws InterruptedException If the {@code UGI.doAs()} call was + * @throws InterruptedException If the {@code UGI.callAs()} call was * somehow interrupted. */ public static FileSystem get(final URI uri, final Configuration conf, @@ -580,7 +580,7 @@ public static FileSystem get(URI uri, Configuration conf) throws IOException { * @param user to perform the get as * @return filesystem instance * @throws IOException if the FileSystem cannot be instantiated. - * @throws InterruptedException If the {@code UGI.doAs()} call was + * @throws InterruptedException If the {@code UGI.callAs()} call was * somehow interrupted. */ public static FileSystem newInstance(final URI uri, final Configuration conf, diff --git a/src/main/java/org/apache/hadoop/security/UserGroupInformation.java b/src/main/java/org/apache/hadoop/security/UserGroupInformation.java index 882e87a..db6c170 100644 --- a/src/main/java/org/apache/hadoop/security/UserGroupInformation.java +++ b/src/main/java/org/apache/hadoop/security/UserGroupInformation.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.security; -import static java.util.Objects.requireNonNull; import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN_DEFAULT; @@ -26,7 +25,6 @@ import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_TOKEN_FILES; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_TOKENS; import static org.apache.hadoop.security.UGIExceptionMessages.*; -import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS; import static org.apache.hadoop.util.PlatformName.IBM_JAVA; import static org.apache.hadoop.util.StringUtils.getTrimmedStringCollection; @@ -84,21 +82,26 @@ import org.apache.hadoop.metrics2.lib.MutableGaugeLong; import org.apache.hadoop.metrics2.lib.MutableQuantiles; import org.apache.hadoop.metrics2.lib.MutableRate; +import org.apache.hadoop.fs.CommonConfigurationKeysPublic; import org.apache.hadoop.security.SaslRpcServer.AuthMethod; import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.apache.hadoop.security.authentication.util.SubjectUtil; +import org.apache.hadoop.security.ssl.X509SecurityMaterial; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.hadoop.util.Shell; import org.apache.hadoop.util.Time; import io.hops.security.GroupAlreadyExistsException; +import io.hops.security.SuperuserKeystoresLoader; import io.hops.security.UserAlreadyExistsException; import io.hops.security.UserAlreadyInGroupException; import io.hops.security.UsersGroups; +import org.apache.hadoop.thirdparty.com.google.common.base.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sun.security.x509.X500Name; /** * User and group information for Hadoop. @@ -214,6 +217,28 @@ public boolean commit() throws LoginException { } user = envUser == null ? null : new User(envUser); } + if (conf.getBoolean(CommonConfigurationKeysPublic.IPC_SERVER_SSL_ENABLED, + CommonConfigurationKeysPublic.IPC_SERVER_SSL_ENABLED_DEFAULT) + && user == null) { + user = getCanonicalUser(KEYSTORE_PRINCIPAL_CLASS); + if (user != null) { + String subject = user.getName(); + LOG.debug("X500 subject is " + subject); + try { + X500Name name = new X500Name(user.getName()); + String username = name.getLocality(); + if (username == null) { + username = name.getCommonName(); + } + if (username != null) { + LOG.debug("New local user with username " + username); + user = new User(username); + } + } catch (IOException ex) { + throw (LoginException)(new LoginException(ex.toString()).initCause(ex)); + } + } + } // use the OS user if (user == null) { user = getCanonicalUser(OS_PRINCIPAL_CLASS); @@ -430,6 +455,7 @@ static Optional getKerberosLoginRenewalExecutor() { private static String OS_LOGIN_MODULE_NAME; private static Class OS_PRINCIPAL_CLASS; + private static Class KEYSTORE_PRINCIPAL_CLASS; private static final boolean windows = System.getProperty("os.name").startsWith("Windows"); @@ -464,9 +490,22 @@ private static Class getOsPrincipalClass() { } return null; } + + @SuppressWarnings("unchecked") + private static Class getKeystorePrincipalClass() { + ClassLoader cl = ClassLoader.getSystemClassLoader(); + try { + return (Class) cl.loadClass("javax.security.auth.x500.X500Principal"); + } catch (ClassNotFoundException e) { + LOG.error("Unable to find JAAS classes:" + e.getMessage()); + } + return null; + } + static { OS_LOGIN_MODULE_NAME = getOSLoginModuleName(); OS_PRINCIPAL_CLASS = getOsPrincipalClass(); + KEYSTORE_PRINCIPAL_CLASS = getKeystorePrincipalClass(); } /** @@ -2324,16 +2363,26 @@ private static class HadoopConfiguration LoginModuleControlFlag.REQUIRED, BASIC_JAAS_OPTIONS); + static final AppConfigurationEntry OPTIONAL_OS_SPECIFIC_LOGIN = + new AppConfigurationEntry( + OS_LOGIN_MODULE_NAME, + LoginModuleControlFlag.OPTIONAL, + BASIC_JAAS_OPTIONS); + static final AppConfigurationEntry HADOOP_LOGIN = new AppConfigurationEntry( HadoopLoginModule.class.getName(), LoginModuleControlFlag.REQUIRED, BASIC_JAAS_OPTIONS); + private static final String FILE_URL = "file://%s"; + private final SuperuserKeystoresLoader keystoresLoader; + private final LoginParams params; HadoopConfiguration(LoginParams params) { this.params = params; + keystoresLoader = new SuperuserKeystoresLoader(conf); } @Override @@ -2358,10 +2407,36 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String appName) { } entries.add(getKerberosEntry()); } + + // In kubernetes we might be running with an arbitrary Container User ID. + // The UnixLoginModule will fail to authenticate the user because the UID + // does not necessarily exist. + // Instead we rely on the provisioned Keystores and the KeystoreLoginModule + // User's username will be the Locality field of certificate's Subject + // See HadoopLoginModule#commit() + String envVariableUsername = System.getenv(HADOOP_USER_NAME); + if (amIRunningInKubernetes() && !Strings.isNullOrEmpty(envVariableUsername)) { + try { + AppConfigurationEntry keystoreEntry = getKeystoreEntry(envVariableUsername); + entries.clear(); + entries.add(OPTIONAL_OS_SPECIFIC_LOGIN); + entries.add(keystoreEntry); + } catch (IOException ex) { + LOG.error("Could not construct path to keystore required for the Keystore LoginModule", ex); + throw new RuntimeException(ex); + } + } + entries.add(HADOOP_LOGIN); + return entries.toArray(new AppConfigurationEntry[0]); } + private boolean amIRunningInKubernetes() { + String kubernetesApiServer = System.getenv("KUBERNETES_SERVICE_HOST"); + return !Strings.isNullOrEmpty(kubernetesApiServer); + } + private AppConfigurationEntry getKerberosEntry() { final Map options = new HashMap<>(BASIC_JAAS_OPTIONS); LoginModuleControlFlag controlFlag = LoginModuleControlFlag.OPTIONAL; @@ -2417,6 +2492,26 @@ private AppConfigurationEntry getKerberosEntry() { KRB5_LOGIN_MODULE, controlFlag, options); } + private AppConfigurationEntry getKeystoreEntry(String username) throws IOException { + X509SecurityMaterial material = keystoresLoader.loadSuperUserMaterial(username); + + String keystoreAlias = getKeystoreAlias(); + Map params = new HashMap<>(); + params.put("keyStoreAlias", keystoreAlias); + params.put("keyStoreURL", String.format(FILE_URL, material.getKeyStoreLocation().toAbsolutePath())); + params.put("keyStorePasswordURL", String.format(FILE_URL, material.getPasswdLocation().toAbsolutePath())); + params.put("privateKeyPasswordURL", String.format(FILE_URL, material.getPasswdLocation().toAbsolutePath())); + + return new AppConfigurationEntry("com.sun.security.auth.module.KeyStoreLoginModule", + LoginModuleControlFlag.OPTIONAL, params); + } + + private String getKeystoreAlias() { + String keystoreAlias = System.getenv("UGI_KEYSTORE_ALIAS"); + return keystoreAlias != null ? keystoreAlias + : System.getProperty("ugi.keystoreloginmodule.alias", "own"); + } + private static String prependFileAuthority(String keytabPath) { return keytabPath.startsWith("file://") ? keytabPath @@ -2424,24 +2519,6 @@ private static String prependFileAuthority(String keytabPath) { } } - public static UserGroupInformation createUserGroupInformationForSubject(Subject subject) { - requireNonNull(subject, "subject is null"); - Set kerberosPrincipals = subject.getPrincipals(KerberosPrincipal.class); - if (kerberosPrincipals.isEmpty()) { - throw new IllegalArgumentException("subject must contain a KerberosPrincipal"); - } - if (kerberosPrincipals.size() != 1) { - throw new IllegalArgumentException("subject must contain only a single KerberosPrincipal"); - } - - KerberosPrincipal principal = kerberosPrincipals.iterator().next(); - User user = new User(principal.getName(), KERBEROS, null); - subject.getPrincipals().add(user); - UserGroupInformation userGroupInformation = new UserGroupInformation(subject); - userGroupInformation.setAuthenticationMethod(KERBEROS); - return userGroupInformation; - } - /** * A test method to print out the current user's UGI. * @param args if there are two arguments, read the user from the keytab