This is an automated email from the ASF dual-hosted git repository. elserj pushed a commit to branch HBASE-26553 in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/HBASE-26553 by this push: new d94c472 HBASE-26667 Integrate user-experience for hbase-client (#4064) d94c472 is described below commit d94c472ef51da3c7913a195e219f8c23265789b3 Author: Andor Molnár <an...@cloudera.com> AuthorDate: Fri Mar 25 23:21:04 2022 +0100 HBASE-26667 Integrate user-experience for hbase-client (#4064) Signed-off-by: Josh Elser <els...@apache.org> --- .../hadoop/hbase/client/ConnectionFactory.java | 18 +++- .../OAuthBearerSaslAuthenticationProvider.java | 2 +- .../provider/OAuthBearerSaslProviderSelector.java | 5 +- .../hbase/security/token/OAuthBearerTokenUtil.java | 47 ++++++++- .../security/token/TestOAuthBearerTokenUtil.java | 115 +++++++++++++++++++++ .../security/oauthbearer/OAuthBearerUtils.java | 1 + 6 files changed, 181 insertions(+), 7 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionFactory.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionFactory.java index a3cf557..0351c58 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionFactory.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionFactory.java @@ -19,7 +19,6 @@ package org.apache.hadoop.hbase.client; import static org.apache.hadoop.hbase.util.FutureUtils.addListener; - import java.io.IOException; import java.lang.reflect.Constructor; import java.security.PrivilegedExceptionAction; @@ -30,6 +29,7 @@ import org.apache.hadoop.hbase.AuthUtil; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.UserProvider; +import org.apache.hadoop.hbase.security.token.OAuthBearerTokenUtil; import org.apache.hadoop.hbase.trace.TraceUtil; import org.apache.hadoop.hbase.util.FutureUtils; import org.apache.hadoop.hbase.util.ReflectionUtils; @@ -70,7 +70,11 @@ import org.apache.yetus.audience.InterfaceAudience; @InterfaceAudience.Public public class ConnectionFactory { - public static final String HBASE_CLIENT_ASYNC_CONNECTION_IMPL = "hbase.client.async.connection.impl"; + public static final String HBASE_CLIENT_ASYNC_CONNECTION_IMPL = + "hbase.client.async.connection.impl"; + + /** Environment variable for OAuth Bearer token */ + public static final String ENV_OAUTHBEARER_TOKEN = "HBASE_JWT"; /** No public c.tors */ protected ConnectionFactory() { @@ -214,6 +218,11 @@ public class ConnectionFactory { */ public static Connection createConnection(Configuration conf, ExecutorService pool, final User user) throws IOException { + + if (System.getenv().containsKey(ENV_OAUTHBEARER_TOKEN)) { + OAuthBearerTokenUtil.addTokenFromEnvironmentVar(user, System.getenv(ENV_OAUTHBEARER_TOKEN)); + } + Class<?> clazz = conf.getClass(ConnectionUtils.HBASE_CLIENT_CONNECTION_IMPL, ConnectionOverAsyncConnection.class, Connection.class); if (clazz != ConnectionOverAsyncConnection.class) { @@ -293,6 +302,11 @@ public class ConnectionFactory { future.completeExceptionally(new IOException("clusterid came back null")); return; } + + if (System.getenv().containsKey(ENV_OAUTHBEARER_TOKEN)) { + OAuthBearerTokenUtil.addTokenFromEnvironmentVar(user, System.getenv(ENV_OAUTHBEARER_TOKEN)); + } + Class<? extends AsyncConnection> clazz = conf.getClass(HBASE_CLIENT_ASYNC_CONNECTION_IMPL, AsyncConnectionImpl.class, AsyncConnection.class); try { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslAuthenticationProvider.java index 8b4dcfe..315ce98 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslAuthenticationProvider.java @@ -17,7 +17,7 @@ */ package org.apache.hadoop.hbase.security.provider; -import static org.apache.hadoop.hbase.security.token.OAuthBearerTokenUtil.TOKEN_KIND; +import static org.apache.hadoop.hbase.security.oauthbearer.OAuthBearerUtils.TOKEN_KIND; import org.apache.hadoop.security.UserGroupInformation; import org.apache.yetus.audience.InterfaceAudience; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslProviderSelector.java index 88c2eed..bfd7d8a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/OAuthBearerSaslProviderSelector.java @@ -17,7 +17,7 @@ */ package org.apache.hadoop.hbase.security.provider; -import static org.apache.hadoop.hbase.security.token.OAuthBearerTokenUtil.TOKEN_KIND; +import static org.apache.hadoop.hbase.security.oauthbearer.OAuthBearerUtils.TOKEN_KIND; import java.util.Collection; import java.util.Optional; import org.apache.hadoop.conf.Configuration; @@ -35,8 +35,7 @@ public class OAuthBearerSaslProviderSelector extends BuiltInProviderSelector { private static final Logger LOG = LoggerFactory.getLogger(OAuthBearerSaslProviderSelector.class); - private final Text OAUTHBEARER_TOKEN_KIND_TEXT = - new Text(TOKEN_KIND); + private final Text OAUTHBEARER_TOKEN_KIND_TEXT = new Text(TOKEN_KIND); private OAuthBearerSaslClientAuthenticationProvider oauthbearer; @Override public void configure(Configuration conf, diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/token/OAuthBearerTokenUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/token/OAuthBearerTokenUtil.java index d9e42d5..6089637 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/token/OAuthBearerTokenUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/token/OAuthBearerTokenUtil.java @@ -18,11 +18,19 @@ */ package org.apache.hadoop.hbase.security.token; +import static org.apache.hadoop.hbase.client.ConnectionFactory.ENV_OAUTHBEARER_TOKEN; +import static org.apache.hadoop.hbase.security.oauthbearer.OAuthBearerUtils.TOKEN_KIND; import java.security.AccessController; import java.security.PrivilegedAction; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.Optional; import javax.security.auth.Subject; +import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.oauthbearer.OAuthBearerToken; +import org.apache.hadoop.hbase.security.oauthbearer.OAuthBearerUtils; import org.apache.hadoop.hbase.security.oauthbearer.internals.OAuthBearerSaslClientProvider; import org.apache.hadoop.io.Text; import org.apache.hadoop.security.token.Token; @@ -36,7 +44,6 @@ import org.slf4j.LoggerFactory; @InterfaceAudience.Public public final class OAuthBearerTokenUtil { private static final Logger LOG = LoggerFactory.getLogger(OAuthBearerTokenUtil.class); - public static final String TOKEN_KIND = "JWT_AUTH_TOKEN"; static { OAuthBearerSaslClientProvider.initialize(); // not part of public API @@ -68,8 +75,46 @@ public final class OAuthBearerTokenUtil { } }; subject.getPrivateCredentials().add(jwt); + if (LOG.isDebugEnabled()) { + LOG.debug("JWT token has been added to user credentials with expiry {}", + lifetimeMs == 0 ? "0" : Instant.ofEpochMilli(lifetimeMs).toString()); + } return null; } }); } + + /** + * Check whether an OAuth Beaerer token is provided in environment variable HBASE_JWT. + * Parse and add it to user private credentials, but only if another token is not already present. + */ + public static void addTokenFromEnvironmentVar(User user, String token) { + Optional<Token<?>> oauthBearerToken = user.getTokens().stream() + .filter((t) -> new Text(OAuthBearerUtils.TOKEN_KIND).equals(t.getKind())) + .findFirst(); + + if (oauthBearerToken.isPresent()) { + LOG.warn("Ignoring OAuth Bearer token in " + ENV_OAUTHBEARER_TOKEN + " environment " + + "variable, because another token is already present"); + return; + } + + String[] tokens = token.split(","); + if (StringUtils.isEmpty(tokens[0])) { + return; + } + long lifetimeMs = 0; + if (tokens.length > 1) { + try { + ZonedDateTime lifetime = ZonedDateTime.parse(tokens[1]); + lifetimeMs = lifetime.toInstant().toEpochMilli(); + } catch (DateTimeParseException e) { + throw new RuntimeException("Unable to parse JWT expiry: " + tokens[1], e); + } + } else { + throw new RuntimeException("Expiry information of JWT is missing"); + } + + addTokenForUser(user, tokens[0], lifetimeMs); + } } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/token/TestOAuthBearerTokenUtil.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/token/TestOAuthBearerTokenUtil.java new file mode 100644 index 0000000..f5109d0 --- /dev/null +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/token/TestOAuthBearerTokenUtil.java @@ -0,0 +1,115 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.hadoop.hbase.security.token; + +import static org.apache.hadoop.hbase.security.oauthbearer.OAuthBearerUtils.TOKEN_KIND; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.time.Instant; +import java.util.Optional; +import java.util.Set; +import javax.security.auth.Subject; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.security.oauthbearer.OAuthBearerToken; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.token.Token; +import org.junit.Test; + +public class TestOAuthBearerTokenUtil { + + @Test + public void testAddTokenFromEnvVar() { + // Arrange + User user = User.createUserForTesting(HBaseConfiguration.create(), "testuser", new String[] {}); + String testToken = "some_base64_encoded_stuff,2022-01-25T16:59:48.614000+00:00"; + + // Act + OAuthBearerTokenUtil.addTokenFromEnvironmentVar(user, testToken); + + // Assert + Optional<Token<?>> oauthBearerToken = user.getTokens().stream() + .filter((t) -> new Text(TOKEN_KIND).equals(t.getKind())) + .findFirst(); + assertTrue("Token cannot be found in user tokens", oauthBearerToken.isPresent()); + user.runAs(new PrivilegedAction<Object>() { + @Override public Object run() { + Subject subject = Subject.getSubject(AccessController.getContext()); + Set<OAuthBearerToken> tokens = subject.getPrivateCredentials(OAuthBearerToken.class); + assertFalse("Token cannot be found in subject's private credentials", tokens.isEmpty()); + OAuthBearerToken jwt = tokens.iterator().next(); + assertEquals("Invalid encoded JWT value", "some_base64_encoded_stuff", jwt.value()); + assertEquals("Invalid JWT expiry", "2022-01-25T16:59:48.614Z", + Instant.ofEpochMilli(jwt.lifetimeMs()).toString()); + return null; + } + }); + } + + @Test(expected = RuntimeException.class) + public void testAddTokenEnvVarWithoutExpiry() { + // Arrange + User user = User.createUserForTesting(new HBaseConfiguration(), "testuser", new String[] {}); + String testToken = "some_base64_encoded_stuff"; + + // Act + OAuthBearerTokenUtil.addTokenFromEnvironmentVar(user, testToken); + + // Assert + } + + @Test(expected = RuntimeException.class) + public void testAddTokenEnvVarWithInvalidExpiry() { + // Arrange + User user = User.createUserForTesting(new HBaseConfiguration(), "testuser", new String[] {}); + String testToken = "some_base64_encoded_stuff,foobarblahblah328742"; + + // Act + OAuthBearerTokenUtil.addTokenFromEnvironmentVar(user, testToken); + + // Assert + } + + @Test + public void testAddTokenEnvVarTokenAlreadyPresent() { + // Arrange + User user = User.createUserForTesting(new HBaseConfiguration(), "testuser", new String[] {}); + user.addToken(new Token<>(null, null, new Text(TOKEN_KIND), null)); + String testToken = "some_base64_encoded_stuff,foobarblahblah328742"; + + // Act + OAuthBearerTokenUtil.addTokenFromEnvironmentVar(user, testToken); + + // Assert + long numberOfTokens = user.getTokens().stream() + .filter((t) -> new Text(TOKEN_KIND).equals(t.getKind())) + .count(); + assertEquals("Invalid number of tokens on User", 1, numberOfTokens); + user.runAs(new PrivilegedAction<Object>() { + @Override public Object run() { + Subject subject = Subject.getSubject(AccessController.getContext()); + Set<OAuthBearerToken> tokens = subject.getPrivateCredentials(OAuthBearerToken.class); + assertTrue("Token should not have been added to subject's credentials", tokens.isEmpty()); + return null; + } + }); + } +} diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/oauthbearer/OAuthBearerUtils.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/oauthbearer/OAuthBearerUtils.java index 19796e8..4b79894 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/oauthbearer/OAuthBearerUtils.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/oauthbearer/OAuthBearerUtils.java @@ -25,6 +25,7 @@ import org.apache.yetus.audience.InterfaceAudience; @InterfaceAudience.Private public final class OAuthBearerUtils { public static final String OAUTHBEARER_MECHANISM = "OAUTHBEARER"; + public static final String TOKEN_KIND = "HBASE_JWT_TOKEN"; /** * Verifies configuration for OAuth Bearer authentication mechanism.