You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by sy...@apache.org on 2016/06/17 05:01:57 UTC
[15/30] hbase git commit: HBASE-5291 Add Kerberos HTTP SPNEGO
authentication support to HBase web consoles (Josh Elser)
HBASE-5291 Add Kerberos HTTP SPNEGO authentication support to HBase web consoles (Josh Elser)
Project: http://git-wip-us.apache.org/repos/asf/hbase/repo
Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/ae5fe1e6
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/ae5fe1e6
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/ae5fe1e6
Branch: refs/heads/hbase-12439
Commit: ae5fe1e61623dd36d33cd7abc94ca5e8229a4672
Parents: 6c60bc9
Author: tedyu <yu...@gmail.com>
Authored: Wed Jun 15 09:26:44 2016 -0700
Committer: tedyu <yu...@gmail.com>
Committed: Wed Jun 15 09:26:44 2016 -0700
----------------------------------------------------------------------
hbase-server/pom.xml | 22 ++
.../apache/hadoop/hbase/http/HttpServer.java | 93 ++++++-
.../apache/hadoop/hbase/http/InfoServer.java | 9 +
.../hbase/regionserver/HRegionServer.java | 1 +
.../hbase/http/HttpServerFunctionalTest.java | 43 ++++
.../hadoop/hbase/http/TestSpnegoHttpServer.java | 258 +++++++++++++++++++
pom.xml | 17 ++
src/main/asciidoc/_chapters/security.adoc | 53 ++++
8 files changed, 486 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/hbase-server/pom.xml
----------------------------------------------------------------------
diff --git a/hbase-server/pom.xml b/hbase-server/pom.xml
index 9532d1e..8d1527f 100644
--- a/hbase-server/pom.xml
+++ b/hbase-server/pom.xml
@@ -572,6 +572,28 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-simplekdc</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <!-- Overriden to get some SPNEGO classes only in newer version -->
+ <version>4.5.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<profiles>
<!-- Needs to make the profile in apache parent pom -->
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/hbase-server/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
index 667e597..e8f875e 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
@@ -27,6 +27,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@@ -102,11 +103,30 @@ import com.sun.jersey.spi.container.servlet.ServletContainer;
@InterfaceStability.Evolving
public class HttpServer implements FilterContainer {
private static final Log LOG = LogFactory.getLog(HttpServer.class);
+ private static final String EMPTY_STRING = "";
static final String FILTER_INITIALIZERS_PROPERTY
= "hbase.http.filter.initializers";
static final String HTTP_MAX_THREADS = "hbase.http.max.threads";
+ public static final String HTTP_UI_AUTHENTICATION = "hbase.security.authentication.ui";
+ static final String HTTP_AUTHENTICATION_PREFIX = "hbase.security.authentication.spnego.";
+ static final String HTTP_SPNEGO_AUTHENTICATION_PREFIX = HTTP_AUTHENTICATION_PREFIX
+ + "spnego.";
+ static final String HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX = "kerberos.principal";
+ public static final String HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY =
+ HTTP_SPNEGO_AUTHENTICATION_PREFIX + HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX;
+ static final String HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX = "kerberos.keytab";
+ public static final String HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY =
+ HTTP_SPNEGO_AUTHENTICATION_PREFIX + HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX;
+ static final String HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX = "kerberos.name.rules";
+ public static final String HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_KEY =
+ HTTP_SPNEGO_AUTHENTICATION_PREFIX + HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX;
+ static final String HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX =
+ "signature.secret.file";
+ public static final String HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_KEY =
+ HTTP_AUTHENTICATION_PREFIX + HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX;
+
// The ServletContext attribute where the daemon Configuration
// gets stored.
public static final String CONF_CONTEXT_ATTRIBUTE = "hbase.conf";
@@ -175,6 +195,9 @@ public class HttpServer implements FilterContainer {
// The -keypass option in keytool
private String keyPassword;
+ private String kerberosNameRulesKey;
+ private String signatureSecretFileKey;
+
@Deprecated
private String name;
@Deprecated
@@ -302,6 +325,16 @@ public class HttpServer implements FilterContainer {
return this;
}
+ public Builder setKerberosNameRulesKey(String kerberosNameRulesKey) {
+ this.kerberosNameRulesKey = kerberosNameRulesKey;
+ return this;
+ }
+
+ public Builder setSignatureSecretFileKey(String signatureSecretFileKey) {
+ this.signatureSecretFileKey = signatureSecretFileKey;
+ return this;
+ }
+
public Builder setAppDir(String appDir) {
this.appDir = appDir;
return this;
@@ -344,7 +377,8 @@ public class HttpServer implements FilterContainer {
HttpServer server = new HttpServer(this);
if (this.securityEnabled) {
- server.initSpnego(conf, hostName, usernameConfKey, keytabConfKey);
+ server.initSpnego(conf, hostName, usernameConfKey, keytabConfKey, kerberosNameRulesKey,
+ signatureSecretFileKey);
}
if (connector != null) {
@@ -927,21 +961,60 @@ public class HttpServer implements FilterContainer {
}
private void initSpnego(Configuration conf, String hostName,
- String usernameConfKey, String keytabConfKey) throws IOException {
+ String usernameConfKey, String keytabConfKey, String kerberosNameRuleKey,
+ String signatureSecretKeyFileKey) throws IOException {
Map<String, String> params = new HashMap<String, String>();
- String principalInConf = conf.get(usernameConfKey);
- if (principalInConf != null && !principalInConf.isEmpty()) {
- params.put("kerberos.principal", SecurityUtil.getServerPrincipal(
+ String principalInConf = getOrEmptyString(conf, usernameConfKey);
+ if (!principalInConf.isEmpty()) {
+ params.put(HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX, SecurityUtil.getServerPrincipal(
principalInConf, hostName));
}
- String httpKeytab = conf.get(keytabConfKey);
- if (httpKeytab != null && !httpKeytab.isEmpty()) {
- params.put("kerberos.keytab", httpKeytab);
+ String httpKeytab = getOrEmptyString(conf, keytabConfKey);
+ if (!httpKeytab.isEmpty()) {
+ params.put(HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX, httpKeytab);
+ }
+ String kerberosNameRule = getOrEmptyString(conf, kerberosNameRuleKey);
+ if (!kerberosNameRule.isEmpty()) {
+ params.put(HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX, kerberosNameRule);
+ }
+ String signatureSecretKeyFile = getOrEmptyString(conf, signatureSecretKeyFileKey);
+ if (!signatureSecretKeyFile.isEmpty()) {
+ params.put(HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX,
+ signatureSecretKeyFile);
}
params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
- defineFilter(webAppContext, SPNEGO_FILTER,
- AuthenticationFilter.class.getName(), params, null);
+ // Verify that the required options were provided
+ if (isMissing(params.get(HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX)) ||
+ isMissing(params.get(HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX))) {
+ throw new IllegalArgumentException(usernameConfKey + " and "
+ + keytabConfKey + " are both required in the configuration "
+ + "to enable SPNEGO/Kerberos authentication for the Web UI");
+ }
+
+ addGlobalFilter(SPNEGO_FILTER, AuthenticationFilter.class.getName(), params);
+ }
+
+ /**
+ * Returns true if the argument is non-null and not whitespace
+ */
+ private boolean isMissing(String value) {
+ if (null == value) {
+ return true;
+ }
+ return value.trim().isEmpty();
+ }
+
+ /**
+ * Extracts the value for the given key from the configuration of returns a string of
+ * zero length.
+ */
+ private String getOrEmptyString(Configuration conf, String key) {
+ if (null == key) {
+ return EMPTY_STRING;
+ }
+ final String value = conf.get(key.trim());
+ return null == value ? EMPTY_STRING : value;
}
/**
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/hbase-server/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
index 5ff6370..0f6c3dd 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
@@ -75,6 +75,15 @@ public class InfoServer {
HBaseConfiguration.getPassword(c, "ssl.server.truststore.password", null),
c.get("ssl.server.truststore.type", "jks"));
}
+ // Enable SPNEGO authentication
+ if ("kerberos".equalsIgnoreCase(c.get(HttpServer.HTTP_UI_AUTHENTICATION, null))) {
+ builder.setUsernameConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY)
+ .setKeytabConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY)
+ .setKerberosNameRulesKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_KEY)
+ .setSignatureSecretFileKey(
+ HttpServer.HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_KEY)
+ .setSecurityEnabled(true);
+ }
this.httpServer = builder.build();
}
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
index b97c23f..45a1095 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
@@ -104,6 +104,7 @@ import org.apache.hadoop.hbase.exceptions.UnknownProtocolException;
import org.apache.hadoop.hbase.executor.ExecutorService;
import org.apache.hadoop.hbase.executor.ExecutorType;
import org.apache.hadoop.hbase.fs.HFileSystem;
+import org.apache.hadoop.hbase.http.HttpServer;
import org.apache.hadoop.hbase.http.InfoServer;
import org.apache.hadoop.hbase.io.hfile.CacheConfig;
import org.apache.hadoop.hbase.io.hfile.HFile;
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/hbase-server/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
index 5c832be..7e773b7 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
@@ -27,6 +27,7 @@ import org.apache.hadoop.hbase.http.HttpServer.Builder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.net.ServerSocket;
import java.net.URI;
import java.net.URL;
import java.net.MalformedURLException;
@@ -94,6 +95,15 @@ public class HttpServerFunctionalTest extends Assert {
return createServer(TEST, conf, pathSpecs);
}
+ public static HttpServer createTestServerWithSecurity(Configuration conf) throws IOException {
+ prepareTestWebapp();
+ return localServerBuilder(TEST).setFindPort(true).setConf(conf).setSecurityEnabled(true)
+ // InfoServer normally sets these for us
+ .setUsernameConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY)
+ .setKeytabConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY)
+ .build();
+ }
+
/**
* Prepare the test webapp by creating the directory from the test properties
* fail if the directory cannot be created.
@@ -226,4 +236,37 @@ public class HttpServerFunctionalTest extends Assert {
}
return out.toString();
}
+
+ /**
+ * Recursively deletes a {@link File}.
+ */
+ protected static void deleteRecursively(File d) {
+ if (d.isDirectory()) {
+ for (String name : d.list()) {
+ File child = new File(d, name);
+ if (child.isFile()) {
+ child.delete();
+ } else {
+ deleteRecursively(d);
+ }
+ }
+ }
+ d.delete();
+ }
+
+ /**
+ * Picks a free port on the host by binding a Socket to '0'.
+ */
+ protected static int getFreePort() throws IOException {
+ ServerSocket s = new ServerSocket(0);
+ try {
+ s.setReuseAddress(true);
+ int port = s.getLocalPort();
+ return port;
+ } finally {
+ if (null != s) {
+ s.close();
+ }
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/hbase-server/src/test/java/org/apache/hadoop/hbase/http/TestSpnegoHttpServer.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/http/TestSpnegoHttpServer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/http/TestSpnegoHttpServer.java
new file mode 100644
index 0000000..a84895f
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/http/TestSpnegoHttpServer.java
@@ -0,0 +1,258 @@
+/*
+ * 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.http;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+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.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.http.TestHttpServer.EchoServlet;
+import org.apache.hadoop.hbase.http.resource.JerseyResource;
+import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.hadoop.security.authentication.util.KerberosName;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthSchemeProvider;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.KerberosCredentials;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.AuthSchemes;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.config.Lookup;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.impl.auth.SPNegoSchemeFactory;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.client.JaasKrbUtil;
+import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
+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.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * Test class for SPNEGO authentication on the HttpServer. Uses Kerby's MiniKDC and Apache
+ * HttpComponents to verify that a simple Servlet is reachable via SPNEGO and unreachable w/o.
+ */
+@Category({MiscTests.class, SmallTests.class})
+public class TestSpnegoHttpServer extends HttpServerFunctionalTest {
+ private static final Log LOG = LogFactory.getLog(TestSpnegoHttpServer.class);
+ private static final String KDC_SERVER_HOST = "localhost";
+ private static final String CLIENT_PRINCIPAL = "client";
+
+ private static HttpServer server;
+ private static URL baseUrl;
+ private static SimpleKdcServer kdc;
+ private static File infoServerKeytab;
+ private static File clientKeytab;
+
+ @BeforeClass
+ public static void setupServer() throws Exception {
+ final String serverPrincipal = "HTTP/" + KDC_SERVER_HOST;
+ final File target = new File(System.getProperty("user.dir"), "target");
+ assertTrue(target.exists());
+
+ kdc = buildMiniKdc();
+ kdc.start();
+
+ File keytabDir = new File(target, TestSpnegoHttpServer.class.getSimpleName()
+ + "_keytabs");
+ if (keytabDir.exists()) {
+ deleteRecursively(keytabDir);
+ }
+ keytabDir.mkdirs();
+
+ infoServerKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + ".keytab");
+ clientKeytab = new File(keytabDir, CLIENT_PRINCIPAL + ".keytab");
+
+ setupUser(kdc, clientKeytab, CLIENT_PRINCIPAL);
+ setupUser(kdc, infoServerKeytab, serverPrincipal);
+
+ Configuration conf = buildSpnegoConfiguration(serverPrincipal, infoServerKeytab);
+
+ server = createTestServerWithSecurity(conf);
+ server.addServlet("echo", "/echo", EchoServlet.class);
+ server.addJerseyResourcePackage(JerseyResource.class.getPackage().getName(), "/jersey/*");
+ server.start();
+ baseUrl = getServerURL(server);
+
+ LOG.info("HTTP server started: "+ baseUrl);
+ }
+
+ @AfterClass
+ public static void stopServer() throws Exception {
+ try {
+ if (null != server) {
+ server.stop();
+ }
+ } catch (Exception e) {
+ LOG.info("Failed to stop info server", e);
+ }
+ try {
+ if (null != kdc) {
+ kdc.stop();
+ }
+ } catch (Exception e) {
+ LOG.info("Failed to stop mini KDC", e);
+ }
+ }
+
+ 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 {
+ SimpleKdcServer kdc = new SimpleKdcServer();
+
+ final File target = new File(System.getProperty("user.dir"), "target");
+ File kdcDir = new File(target, TestSpnegoHttpServer.class.getSimpleName());
+ if (kdcDir.exists()) {
+ deleteRecursively(kdcDir);
+ }
+ kdcDir.mkdirs();
+ kdc.setWorkDir(kdcDir);
+
+ kdc.setKdcHost(KDC_SERVER_HOST);
+ int kdcPort = getFreePort();
+ kdc.setAllowTcp(true);
+ kdc.setAllowUdp(false);
+ kdc.setKdcTcpPort(kdcPort);
+
+ LOG.info("Starting KDC server at " + KDC_SERVER_HOST + ":" + kdcPort);
+
+ kdc.init();
+
+ return kdc;
+ }
+
+ private static Configuration buildSpnegoConfiguration(String serverPrincipal, File
+ serverKeytab) {
+ Configuration conf = new Configuration();
+ KerberosName.setRules("DEFAULT");
+
+ conf.setInt(HttpServer.HTTP_MAX_THREADS, 10);
+
+ // Enable Kerberos (pre-req)
+ conf.set("hbase.security.authentication", "kerberos");
+ conf.set(HttpServer.HTTP_UI_AUTHENTICATION, "kerberos");
+ conf.set(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY, serverPrincipal);
+ conf.set(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY, serverKeytab.getAbsolutePath());
+
+ return conf;
+ }
+
+ @Test
+ public void testUnauthorizedClientsDisallowed() throws IOException {
+ URL url = new URL(getServerURL(server), "/echo?a=b");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
+ }
+
+ @Test
+ public void testAllowedClient() throws Exception {
+ // Create the subject for the client
+ final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(CLIENT_PRINCIPAL, clientKeytab);
+ final Set<Principal> clientPrincipals = clientSubject.getPrincipals();
+ // Make sure the subject has a principal
+ assertFalse(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(privateCredentials.isEmpty());
+ KerberosTicket tgt = privateCredentials.iterator().next();
+ assertNotNull(tgt);
+
+ // The name of the principal
+ final String principalName = clientPrincipals.iterator().next().getName();
+
+ // Run this code, logged in as the subject (the client)
+ HttpResponse resp = Subject.doAs(clientSubject,
+ new PrivilegedExceptionAction<HttpResponse>() {
+ @Override
+ public HttpResponse 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(principalName, GSSName.NT_USER_NAME);
+ GSSCredential credential = gssManager.createCredential(gssClient,
+ GSSCredential.DEFAULT_LIFETIME, oid, GSSCredential.INITIATE_ONLY);
+
+ HttpClientContext context = HttpClientContext.create();
+ Lookup<AuthSchemeProvider> authRegistry = RegistryBuilder.<AuthSchemeProvider>create()
+ .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true, true))
+ .build();
+
+ HttpClient client = HttpClients.custom().setDefaultAuthSchemeRegistry(authRegistry).build();
+ BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(AuthScope.ANY, new KerberosCredentials(credential));
+
+ URL url = new URL(getServerURL(server), "/echo?a=b");
+ context.setTargetHost(new HttpHost(url.getHost(), url.getPort()));
+ context.setCredentialsProvider(credentialsProvider);
+ context.setAuthSchemeRegistry(authRegistry);
+
+ HttpGet get = new HttpGet(url.toURI());
+ return client.execute(get, context);
+ }
+ });
+
+ assertNotNull(resp);
+ assertEquals(HttpURLConnection.HTTP_OK, resp.getStatusLine().getStatusCode());
+ assertEquals("a:b", EntityUtils.toString(resp.getEntity()).trim());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMissingConfigurationThrowsException() throws Exception {
+ Configuration conf = new Configuration();
+ conf.setInt(HttpServer.HTTP_MAX_THREADS, 10);
+ // Enable Kerberos (pre-req)
+ conf.set("hbase.security.authentication", "kerberos");
+ // Intentionally skip keytab and principal
+
+ HttpServer customServer = createTestServerWithSecurity(conf);
+ customServer.addServlet("echo", "/echo", EchoServlet.class);
+ customServer.addJerseyResourcePackage(JerseyResource.class.getPackage().getName(), "/jersey/*");
+ customServer.start();
+ }
+}
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 93b7a0e..b34401d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1231,6 +1231,7 @@
<!-- Do not use versions earlier than 3.2.2 due to a security vulnerability -->
<collections.version>3.2.2</collections.version>
<httpclient.version>4.3.6</httpclient.version>
+ <httpcore.version>4.4.4</httpcore.version>
<metrics-core.version>3.1.2</metrics-core.version>
<guava.version>12.0.1</guava.version>
<jsr305.version>1.3.9</jsr305.version>
@@ -1260,6 +1261,7 @@
<jcodings.version>1.0.8</jcodings.version>
<spy.version>2.11.6</spy.version>
<bouncycastle.version>1.46</bouncycastle.version>
+ <kerby.version>1.0.0-RC2</kerby.version>
<!-- Plugin Dependencies -->
<maven.assembly.version>2.4</maven.assembly.version>
<maven.antrun.version>1.8</maven.antrun.version>
@@ -1525,6 +1527,11 @@
<version>${httpclient.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ <version>${httpcore.version}</version>
+ </dependency>
+ <dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>${commons-cli.version}</version>
@@ -1806,6 +1813,16 @@
<version>${bouncycastle.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-client</artifactId>
+ <version>${kerby.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-simplekdc</artifactId>
+ <version>${kerby.version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>
<!-- Dependencies needed by subprojects -->
http://git-wip-us.apache.org/repos/asf/hbase/blob/ae5fe1e6/src/main/asciidoc/_chapters/security.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/_chapters/security.adoc b/src/main/asciidoc/_chapters/security.adoc
index 0d1407a..85e503c 100644
--- a/src/main/asciidoc/_chapters/security.adoc
+++ b/src/main/asciidoc/_chapters/security.adoc
@@ -69,6 +69,59 @@ See Nick Dimiduk's contribution on this link:http://stackoverflow.com/questions/
If you know how to fix this without opening a second port for HTTPS, patches are appreciated.
====
+[[hbase.secure.spnego.ui]]
+== Using SPNEGO for Kerberos authentication with Web UIs
+
+Kerberos-authentication to HBase Web UIs can be enabled via configuring SPNEGO with the `hbase.security.authentication.ui`
+property in _hbase-site.xml_. Enabling this authentication requires that HBase is also configured to use Kerberos authentication
+for RPCs (e.g `hbase.security.authentication` = `kerberos`).
+
+[source,xml]
+----
+<property>
+ <name>hbase.security.authentication.ui</name>
+ <value>kerberos</value>
+ <description>Controls what kind of authentication should be used for the HBase web UIs.</description>
+</property>
+<property>
+ <name>hbase.security.authentication</name>
+ <value>kerberos</value>
+ <description>The Kerberos keytab file to use for SPNEGO authentication by the web server.</description>
+</property>
+----
+
+A number of properties exist to configure SPNEGO authentication for the web server:
+
+[source,xml]
+----
+<property>
+ <name>hbase.security.authentication.spnego.kerberos.principal</name>
+ <value>HTTP/_HOST@EXAMPLE.COM</value>
+ <description>Required for SPNEGO, the Kerberos principal to use for SPNEGO authentication by the
+ web server. The _HOST keyword will be automatically substituted with the node's
+ hostname.</description>
+</property>
+<property>
+ <name>hbase.security.authentication.spnego.kerberos.keytab</name>
+ <value>/etc/security/keytabs/spnego.service.keytab</value>
+ <description>Required for SPNEGO, the Kerberos keytab file to use for SPNEGO authentication by the
+ web server.</description>
+</property>
+<property>
+ <name>hbase.security.authentication.spnego.kerberos.name.rules</name>
+ <value></value>
+ <description>Optional, Hadoop-style `auth_to_local` rules which will be parsed and used in the
+ handling of Kerberos principals</description>
+</property>
+<property>
+ <name>hbase.security.authentication.signature.secret.file</name>
+ <value></value>
+ <description>Optional, a file whose contents will be used as a secret to sign the HTTP cookies
+ as a part of the SPNEGO authentication handshake. If this is not provided, Java's `Random` library
+ will be used for the secret.</description>
+</property>
+----
+
[[hbase.secure.configuration]]
== Secure Client Access to Apache HBase