2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package org
.apache
.hadoop
.hbase
.http
;
21 import java
.io
.IOException
;
22 import java
.net
.HttpURLConnection
;
24 import java
.security
.Principal
;
25 import java
.security
.PrivilegedExceptionAction
;
27 import javax
.security
.auth
.Subject
;
28 import javax
.security
.auth
.kerberos
.KerberosTicket
;
29 import org
.apache
.hadoop
.conf
.Configuration
;
30 import org
.apache
.hadoop
.hbase
.HBaseClassTestRule
;
31 import org
.apache
.hadoop
.hbase
.HBaseCommonTestingUtil
;
32 import org
.apache
.hadoop
.hbase
.http
.TestHttpServer
.EchoServlet
;
33 import org
.apache
.hadoop
.hbase
.http
.resource
.JerseyResource
;
34 import org
.apache
.hadoop
.hbase
.testclassification
.MiscTests
;
35 import org
.apache
.hadoop
.hbase
.testclassification
.SmallTests
;
36 import org
.apache
.hadoop
.hbase
.util
.SimpleKdcServerUtil
;
37 import org
.apache
.hadoop
.security
.authentication
.util
.KerberosName
;
38 import org
.apache
.http
.HttpHost
;
39 import org
.apache
.http
.HttpResponse
;
40 import org
.apache
.http
.auth
.AuthSchemeProvider
;
41 import org
.apache
.http
.auth
.AuthScope
;
42 import org
.apache
.http
.auth
.KerberosCredentials
;
43 import org
.apache
.http
.client
.HttpClient
;
44 import org
.apache
.http
.client
.config
.AuthSchemes
;
45 import org
.apache
.http
.client
.methods
.HttpGet
;
46 import org
.apache
.http
.client
.protocol
.HttpClientContext
;
47 import org
.apache
.http
.config
.Lookup
;
48 import org
.apache
.http
.config
.RegistryBuilder
;
49 import org
.apache
.http
.impl
.auth
.SPNegoSchemeFactory
;
50 import org
.apache
.http
.impl
.client
.BasicCredentialsProvider
;
51 import org
.apache
.http
.impl
.client
.HttpClients
;
52 import org
.apache
.http
.util
.EntityUtils
;
53 import org
.apache
.kerby
.kerberos
.kerb
.KrbException
;
54 import org
.apache
.kerby
.kerberos
.kerb
.client
.JaasKrbUtil
;
55 import org
.apache
.kerby
.kerberos
.kerb
.server
.SimpleKdcServer
;
56 import org
.ietf
.jgss
.GSSCredential
;
57 import org
.ietf
.jgss
.GSSManager
;
58 import org
.ietf
.jgss
.GSSName
;
59 import org
.ietf
.jgss
.Oid
;
60 import org
.junit
.AfterClass
;
61 import org
.junit
.BeforeClass
;
62 import org
.junit
.ClassRule
;
63 import org
.junit
.Test
;
64 import org
.junit
.experimental
.categories
.Category
;
65 import org
.slf4j
.Logger
;
66 import org
.slf4j
.LoggerFactory
;
69 * Test class for SPNEGO authentication on the HttpServer. Uses Kerby's MiniKDC and Apache
70 * HttpComponents to verify that a simple Servlet is reachable via SPNEGO and unreachable w/o.
72 @Category({MiscTests
.class, SmallTests
.class})
73 public class TestSpnegoHttpServer
extends HttpServerFunctionalTest
{
75 public static final HBaseClassTestRule CLASS_RULE
=
76 HBaseClassTestRule
.forClass(TestSpnegoHttpServer
.class);
78 private static final Logger LOG
= LoggerFactory
.getLogger(TestSpnegoHttpServer
.class);
79 private static final String KDC_SERVER_HOST
= "localhost";
80 private static final String CLIENT_PRINCIPAL
= "client";
82 private static HttpServer server
;
83 private static URL baseUrl
;
84 private static SimpleKdcServer kdc
;
85 private static File infoServerKeytab
;
86 private static File clientKeytab
;
89 public static void setupServer() throws Exception
{
90 Configuration conf
= new Configuration();
91 HBaseCommonTestingUtil htu
= new HBaseCommonTestingUtil(conf
);
93 final String serverPrincipal
= "HTTP/" + KDC_SERVER_HOST
;
95 kdc
= SimpleKdcServerUtil
.getRunningSimpleKdcServer(new File(htu
.getDataTestDir().toString()),
96 HBaseCommonTestingUtil
::randomFreePort
);
97 File keytabDir
= new File(htu
.getDataTestDir("keytabs").toString());
98 if (keytabDir
.exists()) {
99 deleteRecursively(keytabDir
);
103 infoServerKeytab
= new File(keytabDir
, serverPrincipal
.replace('/', '_') + ".keytab");
104 clientKeytab
= new File(keytabDir
, CLIENT_PRINCIPAL
+ ".keytab");
106 setupUser(kdc
, clientKeytab
, CLIENT_PRINCIPAL
);
107 setupUser(kdc
, infoServerKeytab
, serverPrincipal
);
109 buildSpnegoConfiguration(conf
, serverPrincipal
, infoServerKeytab
);
111 server
= createTestServerWithSecurity(conf
);
112 server
.addUnprivilegedServlet("echo", "/echo", EchoServlet
.class);
113 server
.addJerseyResourcePackage(JerseyResource
.class.getPackage().getName(), "/jersey/*");
115 baseUrl
= getServerURL(server
);
117 LOG
.info("HTTP server started: "+ baseUrl
);
121 public static void stopServer() throws Exception
{
123 if (null != server
) {
126 } catch (Exception e
) {
127 LOG
.info("Failed to stop info server", e
);
133 } catch (Exception e
) {
134 LOG
.info("Failed to stop mini KDC", e
);
138 private static void setupUser(SimpleKdcServer kdc
, File keytab
, String principal
)
139 throws KrbException
{
140 kdc
.createPrincipal(principal
);
141 kdc
.exportPrincipal(principal
, keytab
);
144 private static Configuration
buildSpnegoConfiguration(Configuration conf
, String serverPrincipal
,
146 KerberosName
.setRules("DEFAULT");
148 conf
.setInt(HttpServer
.HTTP_MAX_THREADS
, TestHttpServer
.MAX_THREADS
);
150 // Enable Kerberos (pre-req)
151 conf
.set("hbase.security.authentication", "kerberos");
152 conf
.set(HttpServer
.HTTP_UI_AUTHENTICATION
, "kerberos");
153 conf
.set(HttpServer
.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY
, serverPrincipal
);
154 conf
.set(HttpServer
.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY
, serverKeytab
.getAbsolutePath());
160 public void testUnauthorizedClientsDisallowed() throws IOException
{
161 URL url
= new URL(getServerURL(server
), "/echo?a=b");
162 HttpURLConnection conn
= (HttpURLConnection
) url
.openConnection();
163 assertEquals(HttpURLConnection
.HTTP_UNAUTHORIZED
, conn
.getResponseCode());
167 public void testAllowedClient() throws Exception
{
168 // Create the subject for the client
169 final Subject clientSubject
= JaasKrbUtil
.loginUsingKeytab(CLIENT_PRINCIPAL
, clientKeytab
);
170 final Set
<Principal
> clientPrincipals
= clientSubject
.getPrincipals();
171 // Make sure the subject has a principal
172 assertFalse(clientPrincipals
.isEmpty());
174 // Get a TGT for the subject (might have many, different encryption types). The first should
175 // be the default encryption type.
176 Set
<KerberosTicket
> privateCredentials
=
177 clientSubject
.getPrivateCredentials(KerberosTicket
.class);
178 assertFalse(privateCredentials
.isEmpty());
179 KerberosTicket tgt
= privateCredentials
.iterator().next();
182 // The name of the principal
183 final String principalName
= clientPrincipals
.iterator().next().getName();
185 // Run this code, logged in as the subject (the client)
186 HttpResponse resp
= Subject
.doAs(clientSubject
, new PrivilegedExceptionAction
<HttpResponse
>() {
188 public HttpResponse
run() throws Exception
{
189 // Logs in with Kerberos via GSS
190 GSSManager gssManager
= GSSManager
.getInstance();
191 // jGSS Kerberos login constant
192 Oid oid
= new Oid("1.2.840.113554.1.2.2");
193 GSSName gssClient
= gssManager
.createName(principalName
, GSSName
.NT_USER_NAME
);
194 GSSCredential credential
= gssManager
.createCredential(gssClient
,
195 GSSCredential
.DEFAULT_LIFETIME
, oid
, GSSCredential
.INITIATE_ONLY
);
197 HttpClientContext context
= HttpClientContext
.create();
198 Lookup
<AuthSchemeProvider
> authRegistry
= RegistryBuilder
.<AuthSchemeProvider
>create()
199 .register(AuthSchemes
.SPNEGO
, new SPNegoSchemeFactory(true, true))
202 HttpClient client
= HttpClients
.custom().setDefaultAuthSchemeRegistry(authRegistry
)
204 BasicCredentialsProvider credentialsProvider
= new BasicCredentialsProvider();
205 credentialsProvider
.setCredentials(AuthScope
.ANY
, new KerberosCredentials(credential
));
207 URL url
= new URL(getServerURL(server
), "/echo?a=b");
208 context
.setTargetHost(new HttpHost(url
.getHost(), url
.getPort()));
209 context
.setCredentialsProvider(credentialsProvider
);
210 context
.setAuthSchemeRegistry(authRegistry
);
212 HttpGet get
= new HttpGet(url
.toURI());
213 return client
.execute(get
, context
);
218 assertEquals(HttpURLConnection
.HTTP_OK
, resp
.getStatusLine().getStatusCode());
219 assertEquals("a:b", EntityUtils
.toString(resp
.getEntity()).trim());
222 @Test(expected
= IllegalArgumentException
.class)
223 public void testMissingConfigurationThrowsException() throws Exception
{
224 Configuration conf
= new Configuration();
225 conf
.setInt(HttpServer
.HTTP_MAX_THREADS
, TestHttpServer
.MAX_THREADS
);
226 // Enable Kerberos (pre-req)
227 conf
.set("hbase.security.authentication", "kerberos");
228 // Intentionally skip keytab and principal
230 HttpServer customServer
= createTestServerWithSecurity(conf
);
231 customServer
.addUnprivilegedServlet("echo", "/echo", EchoServlet
.class);
232 customServer
.addJerseyResourcePackage(JerseyResource
.class.getPackage().getName(), "/jersey/*");
233 customServer
.start();