HBASE-23312 HBase Thrift SPNEGO configs (HBASE-19852) should be backwards compatible

HBase Thrift SPNEGO configs should not be required.
The `hbase.thrift.spnego.keytab.file` and
`hbase.thrift.spnego.principal` configs should fall
back to the `hbase.thrift.keytab.file` and
`hbase.thrift.kerberos.principal` configs. This will
avoid any issues during upgrades.

Signed-off-by: Josh Elser <elserj@apache.org>
Amending-author: Josh Elser <elserj@apache.org>

Closes #850
This commit is contained in:
Kevin Risden 2019-11-18 20:28:30 -05:00 committed by Josh Elser
parent dbbba7932c
commit ea6cea846a
4 changed files with 324 additions and 73 deletions

View File

@ -18,9 +18,6 @@
package org.apache.hadoop.hbase.thrift; package org.apache.hadoop.hbase.thrift;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SPNEGO_KEYTAB_FILE_KEY;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SPNEGO_PRINCIPAL_KEY;
import java.io.IOException; import java.io.IOException;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.Base64; import java.util.Base64;
@ -29,7 +26,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.security.SecurityUtil; import org.apache.hadoop.hbase.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.security.authorize.AuthorizationException;
@ -66,25 +62,14 @@ public class ThriftHttpServlet extends TServlet {
public static final String NEGOTIATE = "Negotiate"; public static final String NEGOTIATE = "Negotiate";
public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory, public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory,
UserGroupInformation serviceUGI, Configuration conf, UserGroupInformation serviceUGI, UserGroupInformation httpUGI,
HBaseServiceHandler handler, boolean securityEnabled, boolean doAsEnabled) HBaseServiceHandler handler, boolean securityEnabled, boolean doAsEnabled) {
throws IOException {
super(processor, protocolFactory); super(processor, protocolFactory);
this.serviceUGI = serviceUGI; this.serviceUGI = serviceUGI;
this.httpUGI = httpUGI;
this.handler = handler; this.handler = handler;
this.securityEnabled = securityEnabled; this.securityEnabled = securityEnabled;
this.doAsEnabled = doAsEnabled; this.doAsEnabled = doAsEnabled;
if (securityEnabled) {
// login the spnego principal
UserGroupInformation.setConfiguration(conf);
this.httpUGI = UserGroupInformation.loginUserFromKeytabAndReturnUGI(
conf.get(THRIFT_SPNEGO_PRINCIPAL_KEY),
conf.get(THRIFT_SPNEGO_KEYTAB_FILE_KEY)
);
} else {
this.httpUGI = null;
}
} }
@Override @Override

View File

@ -61,6 +61,8 @@ import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_QOP_KEY;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SELECTOR_NUM; import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SELECTOR_NUM;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT; import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY; import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SPNEGO_KEYTAB_FILE_KEY;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SPNEGO_PRINCIPAL_KEY;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_ENABLED_KEY; import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_ENABLED_KEY;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_EXCLUDE_CIPHER_SUITES_KEY; import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_EXCLUDE_CIPHER_SUITES_KEY;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_EXCLUDE_PROTOCOLS_KEY; import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_EXCLUDE_PROTOCOLS_KEY;
@ -172,6 +174,7 @@ public class ThriftServer extends Configured implements Tool {
protected ThriftMetrics metrics; protected ThriftMetrics metrics;
protected HBaseServiceHandler hbaseServiceHandler; protected HBaseServiceHandler hbaseServiceHandler;
protected UserGroupInformation serviceUGI; protected UserGroupInformation serviceUGI;
protected UserGroupInformation httpUGI;
protected boolean httpEnabled; protected boolean httpEnabled;
protected SaslUtil.QualityOfProtection qop; protected SaslUtil.QualityOfProtection qop;
@ -210,8 +213,19 @@ public class ThriftServer extends Configured implements Tool {
conf.get(THRIFT_DNS_INTERFACE_KEY, "default"), conf.get(THRIFT_DNS_INTERFACE_KEY, "default"),
conf.get(THRIFT_DNS_NAMESERVER_KEY, "default"))); conf.get(THRIFT_DNS_NAMESERVER_KEY, "default")));
userProvider.login(THRIFT_KEYTAB_FILE_KEY, THRIFT_KERBEROS_PRINCIPAL_KEY, host); userProvider.login(THRIFT_KEYTAB_FILE_KEY, THRIFT_KERBEROS_PRINCIPAL_KEY, host);
// Setup the SPNEGO user for HTTP if configured
String spnegoPrincipal = getSpengoPrincipal(conf, host);
String spnegoKeytab = getSpnegoKeytab(conf);
UserGroupInformation.setConfiguration(conf);
// login the SPNEGO principal using UGI to avoid polluting the login user
this.httpUGI = UserGroupInformation.loginUserFromKeytabAndReturnUGI(spnegoPrincipal,
spnegoKeytab);
} }
this.serviceUGI = userProvider.getCurrent().getUGI(); this.serviceUGI = userProvider.getCurrent().getUGI();
if (httpUGI == null) {
this.httpUGI = serviceUGI;
}
this.listenPort = conf.getInt(PORT_CONF_KEY, DEFAULT_LISTEN_PORT); this.listenPort = conf.getInt(PORT_CONF_KEY, DEFAULT_LISTEN_PORT);
this.metrics = createThriftMetrics(conf); this.metrics = createThriftMetrics(conf);
@ -249,6 +263,37 @@ public class ThriftServer extends Configured implements Tool {
pauseMonitor.start(); pauseMonitor.start();
} }
private String getSpengoPrincipal(Configuration conf, String host) throws IOException {
String principal = conf.get(THRIFT_SPNEGO_PRINCIPAL_KEY);
if (principal == null) {
// We cannot use the Hadoop configuration deprecation handling here since
// the THRIFT_KERBEROS_PRINCIPAL_KEY config is still valid for regular Kerberos
// communication. The preference should be to use the THRIFT_SPNEGO_PRINCIPAL_KEY
// config so that THRIFT_KERBEROS_PRINCIPAL_KEY doesn't control both backend
// Kerberos principal and SPNEGO principal.
LOG.info("Using deprecated {} config for SPNEGO principal. Use {} instead.",
THRIFT_KERBEROS_PRINCIPAL_KEY, THRIFT_SPNEGO_PRINCIPAL_KEY);
principal = conf.get(THRIFT_KERBEROS_PRINCIPAL_KEY);
}
// Handle _HOST in principal value
return org.apache.hadoop.security.SecurityUtil.getServerPrincipal(principal, host);
}
private String getSpnegoKeytab(Configuration conf) {
String keytab = conf.get(THRIFT_SPNEGO_KEYTAB_FILE_KEY);
if (keytab == null) {
// We cannot use the Hadoop configuration deprecation handling here since
// the THRIFT_KEYTAB_FILE_KEY config is still valid for regular Kerberos
// communication. The preference should be to use the THRIFT_SPNEGO_KEYTAB_FILE_KEY
// config so that THRIFT_KEYTAB_FILE_KEY doesn't control both backend
// Kerberos keytab and SPNEGO keytab.
LOG.info("Using deprecated {} config for SPNEGO keytab. Use {} instead.",
THRIFT_KEYTAB_FILE_KEY, THRIFT_SPNEGO_KEYTAB_FILE_KEY);
keytab = conf.get(THRIFT_KEYTAB_FILE_KEY);
}
return keytab;
}
protected void startInfoServer() throws IOException { protected void startInfoServer() throws IOException {
// Put up info server. // Put up info server.
int port = conf.getInt(THRIFT_INFO_SERVER_PORT , THRIFT_INFO_SERVER_PORT_DEFAULT); int port = conf.getInt(THRIFT_INFO_SERVER_PORT , THRIFT_INFO_SERVER_PORT_DEFAULT);
@ -316,11 +361,10 @@ public class ThriftServer extends Configured implements Tool {
* Create a Servlet for the http server * Create a Servlet for the http server
* @param protocolFactory protocolFactory * @param protocolFactory protocolFactory
* @return the servlet * @return the servlet
* @throws IOException IOException
*/ */
protected TServlet createTServlet(TProtocolFactory protocolFactory) throws IOException { protected TServlet createTServlet(TProtocolFactory protocolFactory) {
return new ThriftHttpServlet(processor, protocolFactory, serviceUGI, return new ThriftHttpServlet(processor, protocolFactory, serviceUGI, httpUGI,
conf, hbaseServiceHandler, securityEnabled, doAsEnabled); hbaseServiceHandler, securityEnabled, doAsEnabled);
} }
/** /**

View File

@ -0,0 +1,240 @@
/*
* 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.thrift;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SUPPORT_PROXYUSER_KEY;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.nio.file.Paths;
import java.security.Principal;
import java.security.PrivilegedExceptionAction;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.security.HBaseKerberosUtils;
import org.apache.hadoop.hbase.testclassification.ClientTests;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.thrift.generated.Hbase;
import org.apache.hadoop.hbase.util.TableDescriptorChecker;
import org.apache.hadoop.security.authentication.util.KerberosName;
import org.apache.http.HttpHeaders;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.KerberosCredentials;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.kerby.kerberos.kerb.client.JaasKrbUtil;
import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.experimental.categories.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Start the HBase Thrift HTTP server on a random port through the command-line
* interface and talk to it from client side with SPNEGO security enabled.
*
* Supplemental test to TestThriftSpnegoHttpServer which falls back to the original
* Kerberos principal and keytab configuration properties, not the separate
* SPNEGO-specific properties.
*/
@Category({ClientTests.class, LargeTests.class})
public class TestThriftSpnegoHttpFallbackServer extends TestThriftHttpServer {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestThriftSpnegoHttpFallbackServer.class);
private static final Logger LOG =
LoggerFactory.getLogger(TestThriftSpnegoHttpFallbackServer.class);
private static SimpleKdcServer kdc;
private static File serverKeytab;
private static File clientKeytab;
private static String clientPrincipal;
private static String serverPrincipal;
private static String spnegoServerPrincipal;
private static SimpleKdcServer buildMiniKdc() throws Exception {
SimpleKdcServer kdc = new SimpleKdcServer();
File kdcDir = Paths.get(TEST_UTIL.getRandomDir().toString()).toAbsolutePath().toFile();
kdcDir.mkdirs();
kdc.setWorkDir(kdcDir);
kdc.setKdcHost(HConstants.LOCALHOST);
int kdcPort = HBaseTestingUtility.randomFreePort();
kdc.setAllowTcp(true);
kdc.setAllowUdp(false);
kdc.setKdcTcpPort(kdcPort);
LOG.info("Starting KDC server at " + HConstants.LOCALHOST + ":" + kdcPort);
kdc.init();
return kdc;
}
private static void addSecurityConfigurations(Configuration conf) {
KerberosName.setRules("DEFAULT");
HBaseKerberosUtils.setKeytabFileForTesting(serverKeytab.getAbsolutePath());
conf.setBoolean(THRIFT_SUPPORT_PROXYUSER_KEY, true);
conf.setBoolean(Constants.USE_HTTP_CONF_KEY, true);
conf.set(Constants.THRIFT_KERBEROS_PRINCIPAL_KEY, serverPrincipal);
conf.set(Constants.THRIFT_KEYTAB_FILE_KEY, serverKeytab.getAbsolutePath());
HBaseKerberosUtils.setSecuredConfiguration(conf, spnegoServerPrincipal,
spnegoServerPrincipal);
conf.set("hadoop.proxyuser.HTTP.hosts", "*");
conf.set("hadoop.proxyuser.HTTP.groups", "*");
conf.set(Constants.THRIFT_KERBEROS_PRINCIPAL_KEY, spnegoServerPrincipal);
}
@BeforeClass
public static void setUpBeforeClass() throws Exception {
kdc = buildMiniKdc();
kdc.start();
File keytabDir = Paths.get(TEST_UTIL.getRandomDir().toString()).toAbsolutePath().toFile();
keytabDir.mkdirs();
clientPrincipal = "client@" + kdc.getKdcConfig().getKdcRealm();
clientKeytab = new File(keytabDir, clientPrincipal + ".keytab");
kdc.createAndExportPrincipals(clientKeytab, clientPrincipal);
serverPrincipal = "hbase/" + HConstants.LOCALHOST + "@" + kdc.getKdcConfig().getKdcRealm();
serverKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + ".keytab");
spnegoServerPrincipal = "HTTP/" + HConstants.LOCALHOST + "@" + kdc.getKdcConfig().getKdcRealm();
// Add SPNEGO principal to server keytab
kdc.createAndExportPrincipals(serverKeytab, serverPrincipal, spnegoServerPrincipal);
TEST_UTIL.getConfiguration().setBoolean(Constants.USE_HTTP_CONF_KEY, true);
addSecurityConfigurations(TEST_UTIL.getConfiguration());
TestThriftHttpServer.setUpBeforeClass();
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
TestThriftHttpServer.tearDownAfterClass();
try {
if (null != kdc) {
kdc.stop();
kdc = null;
}
} catch (Exception e) {
LOG.info("Failed to stop mini KDC", e);
}
}
@Override
protected void talkToThriftServer(String url, int customHeaderSize) throws Exception {
// Close httpClient and THttpClient automatically on any failures
try (
CloseableHttpClient httpClient = createHttpClient();
THttpClient tHttpClient = new THttpClient(url, httpClient)
) {
tHttpClient.open();
if (customHeaderSize > 0) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < customHeaderSize; i++) {
sb.append("a");
}
tHttpClient.setCustomHeader(HttpHeaders.USER_AGENT, sb.toString());
}
TProtocol prot = new TBinaryProtocol(tHttpClient);
Hbase.Client client = new Hbase.Client(prot);
TestThriftServer.createTestTables(client);
TestThriftServer.checkTableList(client);
TestThriftServer.dropTestTables(client);
}
}
private CloseableHttpClient createHttpClient() throws Exception {
final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(clientPrincipal, clientKeytab);
final Set<Principal> clientPrincipals = clientSubject.getPrincipals();
// Make sure the subject has a principal
assertFalse("Found no client principals in the clientSubject.",
clientPrincipals.isEmpty());
// Get a TGT for the subject (might have many, different encryption types). The first should
// be the default encryption type.
Set<KerberosTicket> privateCredentials =
clientSubject.getPrivateCredentials(KerberosTicket.class);
assertFalse("Found no private credentials in the clientSubject.",
privateCredentials.isEmpty());
KerberosTicket tgt = privateCredentials.iterator().next();
assertNotNull("No kerberos ticket found.", tgt);
// The name of the principal
final String clientPrincipalName = clientPrincipals.iterator().next().getName();
return Subject.doAs(clientSubject, new PrivilegedExceptionAction<CloseableHttpClient>() {
@Override
public CloseableHttpClient run() throws Exception {
// Logs in with Kerberos via GSS
GSSManager gssManager = GSSManager.getInstance();
// jGSS Kerberos login constant
Oid oid = new Oid("1.2.840.113554.1.2.2");
GSSName gssClient = gssManager.createName(clientPrincipalName, GSSName.NT_USER_NAME);
GSSCredential credential = gssManager.createCredential(gssClient,
GSSCredential.DEFAULT_LIFETIME, oid, GSSCredential.INITIATE_ONLY);
Lookup<AuthSchemeProvider> authRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true, true))
.build();
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new KerberosCredentials(credential));
return HttpClients.custom()
.setDefaultAuthSchemeRegistry(authRegistry)
.setDefaultCredentialsProvider(credentialsProvider)
.build();
}
});
}
}

View File

@ -1,29 +1,28 @@
/* /*
* Copyright The Apache Software Foundation * 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
* *
* Licensed to the Apache Software Foundation (ASF) under one or more * http://www.apache.org/licenses/LICENSE-2.0
* 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS,
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* License for the specific language governing permissions and limitations * See the License for the specific language governing permissions and
* under the License. * limitations under the License.
*/ */
package org.apache.hadoop.hbase.thrift; package org.apache.hadoop.hbase.thrift;
import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SUPPORT_PROXYUSER_KEY; import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SUPPORT_PROXYUSER_KEY;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File; import java.io.File;
import java.nio.file.Paths;
import java.security.Principal; import java.security.Principal;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.Set; import java.util.Set;
@ -31,7 +30,6 @@ import java.util.Set;
import javax.security.auth.Subject; import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.kerberos.KerberosTicket;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HBaseTestingUtility;
@ -53,7 +51,6 @@ import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.client.JaasKrbUtil; import org.apache.kerby.kerberos.kerb.client.JaasKrbUtil;
import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer; import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TBinaryProtocol;
@ -92,20 +89,10 @@ public class TestThriftSpnegoHttpServer extends TestThriftHttpServer {
private static String serverPrincipal; private static String serverPrincipal;
private static String spnegoServerPrincipal; private static String spnegoServerPrincipal;
private static void setupUser(SimpleKdcServer kdc, File keytab, String principal)
throws KrbException {
kdc.createPrincipal(principal);
kdc.exportPrincipal(principal, keytab);
}
private static SimpleKdcServer buildMiniKdc() throws Exception { private static SimpleKdcServer buildMiniKdc() throws Exception {
SimpleKdcServer kdc = new SimpleKdcServer(); SimpleKdcServer kdc = new SimpleKdcServer();
final File target = new File(System.getProperty("user.dir"), "target"); File kdcDir = Paths.get(TEST_UTIL.getRandomDir().toString()).toAbsolutePath().toFile();
File kdcDir = new File(target, TestThriftSpnegoHttpServer.class.getSimpleName());
if (kdcDir.exists()) {
FileUtils.deleteDirectory(kdcDir);
}
kdcDir.mkdirs(); kdcDir.mkdirs();
kdc.setWorkDir(kdcDir); kdc.setWorkDir(kdcDir);
@ -126,48 +113,42 @@ public class TestThriftSpnegoHttpServer extends TestThriftHttpServer {
KerberosName.setRules("DEFAULT"); KerberosName.setRules("DEFAULT");
HBaseKerberosUtils.setKeytabFileForTesting(serverKeytab.getAbsolutePath()); HBaseKerberosUtils.setKeytabFileForTesting(serverKeytab.getAbsolutePath());
HBaseKerberosUtils.setSecuredConfiguration(conf, serverPrincipal, spnegoServerPrincipal);
conf.setBoolean(THRIFT_SUPPORT_PROXYUSER_KEY, true); conf.setBoolean(THRIFT_SUPPORT_PROXYUSER_KEY, true);
conf.setBoolean(Constants.USE_HTTP_CONF_KEY, true); conf.setBoolean(Constants.USE_HTTP_CONF_KEY, true);
conf.set("hadoop.proxyuser.hbase.hosts", "*");
conf.set("hadoop.proxyuser.hbase.groups", "*");
conf.set(Constants.THRIFT_KERBEROS_PRINCIPAL_KEY, serverPrincipal); conf.set(Constants.THRIFT_KERBEROS_PRINCIPAL_KEY, serverPrincipal);
conf.set(Constants.THRIFT_KEYTAB_FILE_KEY, serverKeytab.getAbsolutePath()); conf.set(Constants.THRIFT_KEYTAB_FILE_KEY, serverKeytab.getAbsolutePath());
HBaseKerberosUtils.setSecuredConfiguration(conf, serverPrincipal, spnegoServerPrincipal);
conf.set("hadoop.proxyuser.hbase.hosts", "*");
conf.set("hadoop.proxyuser.hbase.groups", "*");
conf.set(Constants.THRIFT_SPNEGO_PRINCIPAL_KEY, spnegoServerPrincipal); conf.set(Constants.THRIFT_SPNEGO_PRINCIPAL_KEY, spnegoServerPrincipal);
conf.set(Constants.THRIFT_SPNEGO_KEYTAB_FILE_KEY, spnegoServerKeytab.getAbsolutePath()); conf.set(Constants.THRIFT_SPNEGO_KEYTAB_FILE_KEY, spnegoServerKeytab.getAbsolutePath());
} }
@BeforeClass @BeforeClass
public static void setUpBeforeClass() throws Exception { public static void setUpBeforeClass() throws Exception {
final File target = new File(System.getProperty("user.dir"), "target");
assertTrue(target.exists());
File keytabDir = new File(target, TestThriftSpnegoHttpServer.class.getSimpleName() +
"_keytabs");
if (keytabDir.exists()) {
FileUtils.deleteDirectory(keytabDir);
}
keytabDir.mkdirs();
kdc = buildMiniKdc(); kdc = buildMiniKdc();
kdc.start(); kdc.start();
File keytabDir = Paths.get(TEST_UTIL.getRandomDir().toString()).toAbsolutePath().toFile();
keytabDir.mkdirs();
clientPrincipal = "client@" + kdc.getKdcConfig().getKdcRealm(); clientPrincipal = "client@" + kdc.getKdcConfig().getKdcRealm();
clientKeytab = new File(keytabDir, clientPrincipal + ".keytab"); clientKeytab = new File(keytabDir, clientPrincipal + ".keytab");
setupUser(kdc, clientKeytab, clientPrincipal); kdc.createAndExportPrincipals(clientKeytab, clientPrincipal);
serverPrincipal = "hbase/" + HConstants.LOCALHOST + "@" + kdc.getKdcConfig().getKdcRealm(); serverPrincipal = "hbase/" + HConstants.LOCALHOST + "@" + kdc.getKdcConfig().getKdcRealm();
serverKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + ".keytab"); serverKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + ".keytab");
setupUser(kdc, serverKeytab, serverPrincipal);
// Setup separate SPNEGO keytab
spnegoServerPrincipal = "HTTP/" + HConstants.LOCALHOST + "@" + kdc.getKdcConfig().getKdcRealm(); spnegoServerPrincipal = "HTTP/" + HConstants.LOCALHOST + "@" + kdc.getKdcConfig().getKdcRealm();
spnegoServerKeytab = new File(keytabDir, spnegoServerPrincipal.replace('/', '_') + ".keytab"); spnegoServerKeytab = new File(keytabDir, spnegoServerPrincipal.replace('/', '_') + ".keytab");
setupUser(kdc, spnegoServerKeytab, spnegoServerPrincipal); kdc.createAndExportPrincipals(spnegoServerKeytab, spnegoServerPrincipal);
kdc.createAndExportPrincipals(serverKeytab, serverPrincipal);
TEST_UTIL.getConfiguration().setBoolean(Constants.USE_HTTP_CONF_KEY, true); TEST_UTIL.getConfiguration().setBoolean(Constants.USE_HTTP_CONF_KEY, true);
TEST_UTIL.getConfiguration().setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, false);
addSecurityConfigurations(TEST_UTIL.getConfiguration()); addSecurityConfigurations(TEST_UTIL.getConfiguration());
TestThriftHttpServer.setUpBeforeClass(); TestThriftHttpServer.setUpBeforeClass();
@ -180,6 +161,7 @@ public class TestThriftSpnegoHttpServer extends TestThriftHttpServer {
try { try {
if (null != kdc) { if (null != kdc) {
kdc.stop(); kdc.stop();
kdc = null;
} }
} catch (Exception e) { } catch (Exception e) {
LOG.info("Failed to stop mini KDC", e); LOG.info("Failed to stop mini KDC", e);
@ -204,11 +186,9 @@ public class TestThriftSpnegoHttpServer extends TestThriftHttpServer {
TProtocol prot = new TBinaryProtocol(tHttpClient); TProtocol prot = new TBinaryProtocol(tHttpClient);
Hbase.Client client = new Hbase.Client(prot); Hbase.Client client = new Hbase.Client(prot);
if (!tableCreated) { TestThriftServer.createTestTables(client);
TestThriftServer.createTestTables(client);
tableCreated = true;
}
TestThriftServer.checkTableList(client); TestThriftServer.checkTableList(client);
TestThriftServer.dropTestTables(client);
} }
} }
@ -216,15 +196,17 @@ public class TestThriftSpnegoHttpServer extends TestThriftHttpServer {
final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(clientPrincipal, clientKeytab); final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(clientPrincipal, clientKeytab);
final Set<Principal> clientPrincipals = clientSubject.getPrincipals(); final Set<Principal> clientPrincipals = clientSubject.getPrincipals();
// Make sure the subject has a principal // Make sure the subject has a principal
assertFalse(clientPrincipals.isEmpty()); assertFalse("Found no client principals in the clientSubject.",
clientPrincipals.isEmpty());
// Get a TGT for the subject (might have many, different encryption types). The first should // Get a TGT for the subject (might have many, different encryption types). The first should
// be the default encryption type. // be the default encryption type.
Set<KerberosTicket> privateCredentials = Set<KerberosTicket> privateCredentials =
clientSubject.getPrivateCredentials(KerberosTicket.class); clientSubject.getPrivateCredentials(KerberosTicket.class);
assertFalse(privateCredentials.isEmpty()); assertFalse("Found no private credentials in the clientSubject.",
privateCredentials.isEmpty());
KerberosTicket tgt = privateCredentials.iterator().next(); KerberosTicket tgt = privateCredentials.iterator().next();
assertNotNull(tgt); assertNotNull("No kerberos ticket found.", tgt);
// The name of the principal // The name of the principal
final String clientPrincipalName = clientPrincipals.iterator().next().getName(); final String clientPrincipalName = clientPrincipals.iterator().next().getName();