From 333edb27c84864a3a505544afe82459b4e709129 Mon Sep 17 00:00:00 2001 From: Elliott Clark Date: Tue, 29 Sep 2015 14:17:24 -0700 Subject: [PATCH] HBASE-14512 Cache UGI groups --- .../org/apache/hadoop/hbase/security/User.java | 37 ++++++++++++++++++- .../org/apache/hadoop/hbase/ipc/CallRunner.java | 2 +- .../org/apache/hadoop/hbase/ipc/RpcServer.java | 41 ++++++++++++---------- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java index b0e2ed2..c7b1072 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java @@ -23,7 +23,12 @@ import java.io.IOException; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; @@ -252,8 +257,29 @@ public abstract class User { * 0.20 and versions 0.21 and above. */ private static final class SecureHadoopUser extends User { + private enum GROUP_CACHE_HOLDER { + INSTANCE; + private final LoadingCache cache = CacheBuilder.newBuilder() + // This is the same cache time as Hadoop. + // Adding the cache allows us to control our own destiny and to keep + // an empty list cached. + // This is much more likely for HBase as it has remote users that might not have + // posix groups on this machine + .expireAfterWrite(10 * 300 * 1000, TimeUnit.MILLISECONDS) + // Set concurrency level equal to the default number of handlers that + // the simple handler spins up. + .concurrencyLevel(20) + // create the loader + // This just delegates to UGI. + .build(new CacheLoader() { + @Override + public String[] load(UserGroupInformation ugi) throws Exception { + return ugi.getGroupNames(); + } + }); + } + private String shortName; - private SecureHadoopUser() throws IOException { ugi = UserGroupInformation.getCurrentUser(); } @@ -275,6 +301,15 @@ public abstract class User { } @Override + public String[] getGroupNames() { + try { + return GROUP_CACHE_HOLDER.INSTANCE.cache.get(ugi); + } catch (ExecutionException e) { + return new String[0]; + } + } + + @Override public T runAs(PrivilegedAction action) { return ugi.doAs(action); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/CallRunner.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/CallRunner.java index 26ffa95..38b7c91 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/CallRunner.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/CallRunner.java @@ -85,7 +85,7 @@ public class CallRunner { this.status.setStatus("Setting up call"); this.status.setConnection(call.connection.getHostAddress(), call.connection.getRemotePort()); if (RpcServer.LOG.isTraceEnabled()) { - UserGroupInformation remoteUser = call.connection.user; + UserGroupInformation remoteUser = call.connection.ugi; RpcServer.LOG.trace(call.toShortString() + " executing as " + ((remoteUser == null) ? "NULL principal" : remoteUser.getUserName())); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java index 8cead2a..16bea72 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java @@ -325,7 +325,7 @@ public class RpcServer implements RpcServerInterface { this.isError = false; this.size = size; this.tinfo = tinfo; - this.user = connection.user == null? null: userProvider.create(connection.user); + this.user = connection.user; this.remoteAddress = remoteAddress; } @@ -541,7 +541,7 @@ public class RpcServer implements RpcServerInterface { } public UserGroupInformation getRemoteUser() { - return connection.user; + return connection.ugi; } @Override @@ -1230,7 +1230,7 @@ public class RpcServer implements RpcServerInterface { */ private CompressionCodec compressionCodec; BlockingService service; - protected UserGroupInformation user = null; + private AuthMethod authMethod; private boolean saslContextEstablished; private boolean skipInitialSaslHandshake; @@ -1253,6 +1253,8 @@ public class RpcServer implements RpcServerInterface { new Call(SASL_CALLID, this.service, null, null, null, null, this, null, 0, null, null); public UserGroupInformation attemptingUser = null; // user name before auth + protected User user = null; + protected UserGroupInformation ugi = null; public Connection(SocketChannel channel, long lastContact) { this.channel = channel; @@ -1433,14 +1435,14 @@ public class RpcServer implements RpcServerInterface { if (saslServer.isComplete()) { String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP); useWrap = qop != null && !"auth".equalsIgnoreCase(qop); - user = getAuthorizedUgi(saslServer.getAuthorizationID()); + ugi = getAuthorizedUgi(saslServer.getAuthorizationID()); if (LOG.isDebugEnabled()) { LOG.debug("SASL server context established. Authenticated client: " - + user + ". Negotiated QoP is " + + ugi + ". Negotiated QoP is " + saslServer.getNegotiatedProperty(Sasl.QOP)); } metrics.authenticationSuccess(); - AUDITLOG.info(AUTH_SUCCESSFUL_FOR + user); + AUDITLOG.info(AUTH_SUCCESSFUL_FOR + ugi); saslContextEstablished = true; } } @@ -1663,32 +1665,32 @@ public class RpcServer implements RpcServerInterface { setupCellBlockCodecs(this.connectionHeader); UserGroupInformation protocolUser = createUser(connectionHeader); if (!useSasl) { - user = protocolUser; - if (user != null) { - user.setAuthenticationMethod(AuthMethod.SIMPLE.authenticationMethod); + ugi = protocolUser; + if (ugi != null) { + ugi.setAuthenticationMethod(AuthMethod.SIMPLE.authenticationMethod); } } else { // user is authenticated - user.setAuthenticationMethod(authMethod.authenticationMethod); + ugi.setAuthenticationMethod(authMethod.authenticationMethod); //Now we check if this is a proxy user case. If the protocol user is //different from the 'user', it is a proxy user scenario. However, //this is not allowed if user authenticated with DIGEST. if ((protocolUser != null) - && (!protocolUser.getUserName().equals(user.getUserName()))) { + && (!protocolUser.getUserName().equals(ugi.getUserName()))) { if (authMethod == AuthMethod.DIGEST) { // Not allowed to doAs if token authentication is used - throw new AccessDeniedException("Authenticated user (" + user + throw new AccessDeniedException("Authenticated user (" + ugi + ") doesn't match what the client claims to be (" + protocolUser + ")"); } else { // Effective user can be different from authenticated user // for simple auth or kerberos auth // The user is the real user. Now we create a proxy user - UserGroupInformation realUser = user; - user = UserGroupInformation.createProxyUser(protocolUser + UserGroupInformation realUser = ugi; + ugi = UserGroupInformation.createProxyUser(protocolUser .getUserName(), realUser); // Now the user is a proxy user, set Authentication method Proxy. - user.setAuthenticationMethod(AuthenticationMethod.PROXY); + ugi.setAuthenticationMethod(AuthenticationMethod.PROXY); } } } @@ -1774,8 +1776,9 @@ public class RpcServer implements RpcServerInterface { // Throw FatalConnectionException wrapping ACE so client does right thing and closes // down the connection instead of trying to read non-existent retun. throw new AccessDeniedException("Connection from " + this + " for service " + - connectionHeader.getServiceName() + " is unauthorized for user: " + user); + connectionHeader.getServiceName() + " is unauthorized for user: " + ugi); } + this.user = userProvider.create(this.ugi); } } @@ -1878,11 +1881,11 @@ public class RpcServer implements RpcServerInterface { // real user for the effective user, therefore not required to // authorize real user. doAs is allowed only for simple or kerberos // authentication - if (user != null && user.getRealUser() != null + if (ugi != null && ugi.getRealUser() != null && (authMethod != AuthMethod.DIGEST)) { - ProxyUsers.authorize(user, this.getHostAddress(), conf); + ProxyUsers.authorize(ugi, this.getHostAddress(), conf); } - authorize(user, connectionHeader, getHostInetAddress()); + authorize(ugi, connectionHeader, getHostInetAddress()); metrics.authorizationSuccess(); } catch (AuthorizationException ae) { if (LOG.isDebugEnabled()) { -- 2.5.3