diff --git hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcClient.java hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcClient.java index a612b18..d063eda 100644 --- hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcClient.java +++ hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcClient.java @@ -70,6 +70,7 @@ import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ResponseHeader; import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.protobuf.generated.TracingProtos.RPCTInfo; import org.apache.hadoop.hbase.security.AuthMethod; +import org.apache.hadoop.hbase.security.HBaseKerberosMultiRealm; import org.apache.hadoop.hbase.security.HBaseSaslRpcClient; import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.hbase.security.User; @@ -417,8 +418,17 @@ public class RpcClient { authMethod = AuthMethod.SIMPLE; } else if (token != null) { authMethod = AuthMethod.DIGEST; - } else { - authMethod = AuthMethod.KERBEROS; + } else if (HBaseKerberosMultiRealm.isUserInADifferentRealm ( + conf,serverPrincipal,ticket)){ + serverPrincipal = HBaseKerberosMultiRealm.getPrincipal(conf); + authMethod = AuthMethod.KERBEROS_USER; + if (LOG.isDebugEnabled()){ + LOG.debug("AuthMehod is KERBEROS_USER and serverPrincipal is changed to " + + serverPrincipal); + } + } + else { + authMethod = AuthMethod.KERBEROS; } if (LOG.isDebugEnabled()) { @@ -455,7 +465,7 @@ public class RpcClient { return null; } UserInformation.Builder userInfoPB = UserInformation.newBuilder(); - if (authMethod == AuthMethod.KERBEROS) { + if (authMethod == AuthMethod.KERBEROS || authMethod == AuthMethod.KERBEROS_USER) { // Send effective user for Kerberos auth userInfoPB.setEffectiveUser(ugi.getUserName()); } else if (authMethod == AuthMethod.SIMPLE) { @@ -747,7 +757,7 @@ public class RpcClient { UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); UserGroupInformation realUser = currentUser.getRealUser(); - return authMethod == AuthMethod.KERBEROS && + return (authMethod == AuthMethod.KERBEROS || authMethod == AuthMethod.KERBEROS_USER) && loginUser != null && //Make sure user logged in using Kerberos either keytab or TGT loginUser.hasKerberosCredentials() && @@ -869,7 +879,7 @@ public class RpcClient { final InputStream in2 = inStream; final OutputStream out2 = outStream; UserGroupInformation ticket = remoteId.getTicket().getUGI(); - if (authMethod == AuthMethod.KERBEROS) { + if (authMethod == AuthMethod.KERBEROS || authMethod == AuthMethod.KERBEROS_USER) { if (ticket != null && ticket.getRealUser() != null) { ticket = ticket.getRealUser(); } diff --git hbase-client/src/main/java/org/apache/hadoop/hbase/security/AuthMethod.java hbase-client/src/main/java/org/apache/hadoop/hbase/security/AuthMethod.java index c223fa5..145454d 100644 --- hbase-client/src/main/java/org/apache/hadoop/hbase/security/AuthMethod.java +++ hbase-client/src/main/java/org/apache/hadoop/hbase/security/AuthMethod.java @@ -29,7 +29,8 @@ import java.io.IOException; public enum AuthMethod { SIMPLE((byte) 80, "", UserGroupInformation.AuthenticationMethod.SIMPLE), KERBEROS((byte) 81, "GSSAPI", UserGroupInformation.AuthenticationMethod.KERBEROS), - DIGEST((byte) 82, "DIGEST-MD5", UserGroupInformation.AuthenticationMethod.TOKEN); + DIGEST((byte) 82, "DIGEST-MD5", UserGroupInformation.AuthenticationMethod.TOKEN), + KERBEROS_USER((byte) 83, "GSSAPI", UserGroupInformation.AuthenticationMethod.KERBEROS); /** The code for this method. */ public final byte code; diff --git hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseKerberosMultiRealm.java hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseKerberosMultiRealm.java new file mode 100644 index 0000000..b8b7483 --- /dev/null +++ hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseKerberosMultiRealm.java @@ -0,0 +1,111 @@ +package org.apache.hadoop.hbase.security; + +/** + * 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. + */ + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.SaslRpcServer; +import org.apache.hadoop.security.UserGroupInformation; + +public class HBaseKerberosMultiRealm { + + private static final Log LOG = LogFactory.getLog(HBaseKerberosMultiRealm.class); + + //configuration property name for the user realm + public static String KERBEROS_USER_REALM_PRINCIPAL ="kerberos.userrealm.principal"; + + // class variable used to store the Subject + private volatile static UserGroupInformation ugi ; + + /** + * checks if the user is in a different realm than server + * @param conf + * @param serverPrincipal + * @param ticket + * @return + */ + public static boolean isUserInADifferentRealm(Configuration conf, String serverPrincipal, + UserGroupInformation ticket) { + if (conf.get(KERBEROS_USER_REALM_PRINCIPAL) != null){ + String fullName = ticket.getUserName(); + if ( ticket.getRealUser() != null){ + fullName = ticket.getRealUser().getUserName(); + } + return isUserInADifferentRealm (serverPrincipal, fullName); + } + return false; + } + + static boolean isUserInADifferentRealm(String serverPrincipal, + String fullName) { + String [] userNames = SaslRpcServer.splitKerberosName(fullName); + String [] serverNames = SaslRpcServer.splitKerberosName(serverPrincipal); + + //check if the principal belongs to server realm + return (!userNames[userNames.length-1].equalsIgnoreCase( + serverNames[serverNames.length-1])); + } + + /** + * return the UGI for server Principal in the user realm + * Once created, the the UGI is cached. + * @param conf + * @return UserGroupInformation + */ + public static UserGroupInformation getServerUGIForUserRealm (Configuration conf){ + if (ugi == null) { + synchronized (HBaseKerberosMultiRealm.class){ + if (ugi == null) { + if (conf.get(KERBEROS_USER_REALM_PRINCIPAL) != null){ + try{ + ugi = UserGroupInformation.loginServerFromCurrentKeytabAndReturnUGI + (conf.get(KERBEROS_USER_REALM_PRINCIPAL)); + } catch (IOException e) { + LOG.warn("Error in loading UGI for the principal in user realm", e); + } + } + // if UGI is still null, go with the current user. + if (ugi == null){ + try { + ugi = UserGroupInformation.getCurrentUser(); + } catch (IOException e1) { + } + } + } + } + } + return ugi; + } + + /** + * returns the configured principal in the User realm. + * This method will be invoked by client side + * @param principalName + * @param conf + * @return string value containing server principal in user realm + */ + public static String getPrincipal(Configuration conf ) { + return conf.get(KERBEROS_USER_REALM_PRINCIPAL); + } + + +} diff --git hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java index 9bbba53..ad1d3b8 100644 --- hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java +++ hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java @@ -80,6 +80,7 @@ public class HBaseSaslRpcClient { SaslUtil.SASL_DEFAULT_REALM, new SaslClientCallbackHandler(token)); break; case KERBEROS: + case KERBEROS_USER: if (LOG.isDebugEnabled()) { LOG .debug("Creating SASL " + AuthMethod.KERBEROS.getMechanismName() diff --git hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java index d7be74a..7b940db 100644 --- hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java +++ hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java @@ -18,7 +18,7 @@ package org.apache.hadoop.hbase.ipc; -import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION; +import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHORIZATION; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -83,6 +83,7 @@ import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ResponseHeader; import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.regionserver.HRegionServer; import org.apache.hadoop.hbase.security.AuthMethod; +import org.apache.hadoop.hbase.security.HBaseKerberosMultiRealm; import org.apache.hadoop.hbase.security.HBasePolicyProvider; import org.apache.hadoop.hbase.security.HBaseSaslRpcServer; import org.apache.hadoop.hbase.security.HBaseSaslRpcServer.SaslDigestCallbackHandler; @@ -121,7 +122,6 @@ import com.google.protobuf.Message; import com.google.protobuf.Message.Builder; import com.google.protobuf.ServiceException; import com.google.protobuf.TextFormat; -// Uses Writables doing sasl /** * An RPC server that hosts protobuf described Services. @@ -1277,28 +1277,14 @@ public class RpcServer implements RpcServerInterface { SaslUtil.SASL_PROPS, new SaslDigestCallbackHandler( secretManager, this)); break; + case KERBEROS_USER: + UserGroupInformation ugi = HBaseKerberosMultiRealm.getServerUGIForUserRealm(conf); + createSaslServer(ugi); + break; default: UserGroupInformation current = UserGroupInformation .getCurrentUser(); - String fullName = current.getUserName(); - if (LOG.isDebugEnabled()) { - LOG.debug("Kerberos principal name is " + fullName); - } - final String names[] = SaslUtil.splitKerberosName(fullName); - if (names.length != 3) { - throw new AccessControlException( - "Kerberos principal name does NOT have the expected " - + "hostname part: " + fullName); - } - current.doAs(new PrivilegedExceptionAction() { - @Override - public Object run() throws SaslException { - saslServer = Sasl.createSaslServer(AuthMethod.KERBEROS - .getMechanismName(), names[0], names[1], - SaslUtil.SASL_PROPS, new SaslGssCallbackHandler()); - return null; - } - }); + createSaslServer(current); } if (saslServer == null) throw new AccessControlException( @@ -1354,6 +1340,29 @@ public class RpcServer implements RpcServerInterface { } } } + + private void createSaslServer(UserGroupInformation ugi) + throws AccessControlException, IOException, InterruptedException { + String fullName = ugi.getUserName(); + if (LOG.isDebugEnabled()) { + LOG.debug("Kerberos principal name is " + fullName); + } + final String names[] = SaslUtil.splitKerberosName(fullName); + if (names.length != 3) { + throw new AccessControlException( + "Kerberos principal name does NOT have the expected " + + "hostname part: " + fullName); + } + ugi.doAs(new PrivilegedExceptionAction() { + @Override + public Object run() throws SaslException { + saslServer = Sasl.createSaslServer(AuthMethod.KERBEROS + .getMechanismName(), names[0], names[1], + SaslUtil.SASL_PROPS, new SaslGssCallbackHandler()); + return null; + } + }); + } /** * No protobuf encoding of raw sasl messages