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:
parent
dbbba7932c
commit
ea6cea846a
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue