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
.net
.HttpURLConnection
;
23 import java
.security
.Principal
;
24 import java
.security
.PrivilegedExceptionAction
;
27 import javax
.security
.auth
.Subject
;
28 import javax
.security
.auth
.kerberos
.KerberosTicket
;
30 import org
.apache
.hadoop
.conf
.Configuration
;
31 import org
.apache
.hadoop
.hbase
.HBaseClassTestRule
;
32 import org
.apache
.hadoop
.hbase
.HBaseCommonTestingUtil
;
33 import org
.apache
.hadoop
.hbase
.http
.TestHttpServer
.EchoServlet
;
34 import org
.apache
.hadoop
.hbase
.http
.resource
.JerseyResource
;
35 import org
.apache
.hadoop
.hbase
.testclassification
.MiscTests
;
36 import org
.apache
.hadoop
.hbase
.testclassification
.SmallTests
;
37 import org
.apache
.hadoop
.hbase
.util
.SimpleKdcServerUtil
;
38 import org
.apache
.hadoop
.security
.authentication
.util
.KerberosName
;
39 import org
.apache
.hadoop
.security
.authorize
.AccessControlList
;
40 import org
.apache
.http
.HttpHost
;
41 import org
.apache
.http
.HttpResponse
;
42 import org
.apache
.http
.auth
.AuthSchemeProvider
;
43 import org
.apache
.http
.auth
.AuthScope
;
44 import org
.apache
.http
.auth
.KerberosCredentials
;
45 import org
.apache
.http
.client
.HttpClient
;
46 import org
.apache
.http
.client
.config
.AuthSchemes
;
47 import org
.apache
.http
.client
.methods
.HttpGet
;
48 import org
.apache
.http
.client
.protocol
.HttpClientContext
;
49 import org
.apache
.http
.config
.Lookup
;
50 import org
.apache
.http
.config
.RegistryBuilder
;
51 import org
.apache
.http
.impl
.auth
.SPNegoSchemeFactory
;
52 import org
.apache
.http
.impl
.client
.BasicCredentialsProvider
;
53 import org
.apache
.http
.impl
.client
.HttpClients
;
54 import org
.apache
.http
.util
.EntityUtils
;
55 import org
.apache
.kerby
.kerberos
.kerb
.KrbException
;
56 import org
.apache
.kerby
.kerberos
.kerb
.client
.JaasKrbUtil
;
57 import org
.apache
.kerby
.kerberos
.kerb
.server
.SimpleKdcServer
;
58 import org
.ietf
.jgss
.GSSCredential
;
59 import org
.ietf
.jgss
.GSSManager
;
60 import org
.ietf
.jgss
.GSSName
;
61 import org
.ietf
.jgss
.Oid
;
62 import org
.junit
.AfterClass
;
63 import org
.junit
.BeforeClass
;
64 import org
.junit
.ClassRule
;
65 import org
.junit
.Test
;
66 import org
.junit
.experimental
.categories
.Category
;
67 import org
.slf4j
.Logger
;
68 import org
.slf4j
.LoggerFactory
;
71 * Test class for SPNEGO Proxyuser authentication on the HttpServer. Uses Kerby's MiniKDC and Apache
72 * HttpComponents to verify that the doas= mechanicsm works, and that the proxyuser settings are
75 @Category({MiscTests
.class, SmallTests
.class})
76 public class TestProxyUserSpnegoHttpServer
extends HttpServerFunctionalTest
{
78 public static final HBaseClassTestRule CLASS_RULE
=
79 HBaseClassTestRule
.forClass(TestProxyUserSpnegoHttpServer
.class);
81 private static final Logger LOG
= LoggerFactory
.getLogger(TestProxyUserSpnegoHttpServer
.class);
82 private static final String KDC_SERVER_HOST
= "localhost";
83 private static final String WHEEL_PRINCIPAL
= "wheel";
84 private static final String UNPRIVILEGED_PRINCIPAL
= "unprivileged";
85 private static final String PRIVILEGED_PRINCIPAL
= "privileged";
86 private static final String PRIVILEGED2_PRINCIPAL
= "privileged2";
88 private static HttpServer server
;
89 private static URL baseUrl
;
90 private static SimpleKdcServer kdc
;
91 private static File infoServerKeytab
;
92 private static File wheelKeytab
;
93 private static File unprivilegedKeytab
;
94 private static File privilegedKeytab
;
95 private static File privileged2Keytab
;
99 public static void setupServer() throws Exception
{
100 Configuration conf
= new Configuration();
101 HBaseCommonTestingUtil htu
= new HBaseCommonTestingUtil(conf
);
103 final String serverPrincipal
= "HTTP/" + KDC_SERVER_HOST
;
105 kdc
= SimpleKdcServerUtil
.getRunningSimpleKdcServer(new File(htu
.getDataTestDir().toString()),
106 HBaseCommonTestingUtil
::randomFreePort
);
107 File keytabDir
= new File(htu
.getDataTestDir("keytabs").toString());
108 if (keytabDir
.exists()) {
109 deleteRecursively(keytabDir
);
113 infoServerKeytab
= new File(keytabDir
, serverPrincipal
.replace('/', '_') + ".keytab");
114 wheelKeytab
= new File(keytabDir
, WHEEL_PRINCIPAL
+ ".keytab");
115 unprivilegedKeytab
= new File(keytabDir
, UNPRIVILEGED_PRINCIPAL
+ ".keytab");
116 privilegedKeytab
= new File(keytabDir
, PRIVILEGED_PRINCIPAL
+ ".keytab");
117 privileged2Keytab
= new File(keytabDir
, PRIVILEGED2_PRINCIPAL
+ ".keytab");
119 setupUser(kdc
, wheelKeytab
, WHEEL_PRINCIPAL
);
120 setupUser(kdc
, unprivilegedKeytab
, UNPRIVILEGED_PRINCIPAL
);
121 setupUser(kdc
, privilegedKeytab
, PRIVILEGED_PRINCIPAL
);
122 setupUser(kdc
, privileged2Keytab
, PRIVILEGED2_PRINCIPAL
);
124 setupUser(kdc
, infoServerKeytab
, serverPrincipal
);
126 buildSpnegoConfiguration(conf
, serverPrincipal
, infoServerKeytab
);
127 AccessControlList acl
= buildAdminAcl(conf
);
129 server
= createTestServerWithSecurityAndAcl(conf
, acl
);
130 server
.addPrivilegedServlet("echo", "/echo", EchoServlet
.class);
131 server
.addJerseyResourcePackage(JerseyResource
.class.getPackage().getName(), "/jersey/*");
133 baseUrl
= getServerURL(server
);
135 LOG
.info("HTTP server started: "+ baseUrl
);
139 public static void stopServer() throws Exception
{
141 if (null != server
) {
144 } catch (Exception e
) {
145 LOG
.info("Failed to stop info server", e
);
151 } catch (Exception e
) {
152 LOG
.info("Failed to stop mini KDC", e
);
156 private static void setupUser(SimpleKdcServer kdc
, File keytab
, String principal
)
157 throws KrbException
{
158 kdc
.createPrincipal(principal
);
159 kdc
.exportPrincipal(principal
, keytab
);
163 protected static Configuration
buildSpnegoConfiguration(Configuration conf
,
164 String serverPrincipal
, File serverKeytab
) {
165 KerberosName
.setRules("DEFAULT");
167 conf
.setInt(HttpServer
.HTTP_MAX_THREADS
, TestHttpServer
.MAX_THREADS
);
169 // Enable Kerberos (pre-req)
170 conf
.set("hbase.security.authentication", "kerberos");
171 conf
.set(HttpServer
.HTTP_UI_AUTHENTICATION
, "kerberos");
172 conf
.set(HttpServer
.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY
, serverPrincipal
);
173 conf
.set(HttpServer
.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY
, serverKeytab
.getAbsolutePath());
175 conf
.set(HttpServer
.HTTP_SPNEGO_AUTHENTICATION_ADMIN_USERS_KEY
, PRIVILEGED_PRINCIPAL
);
176 conf
.set(HttpServer
.HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_KEY
, "true");
177 conf
.set("hadoop.security.authorization", "true");
179 conf
.set("hadoop.proxyuser.wheel.hosts", "*");
180 conf
.set("hadoop.proxyuser.wheel.users", PRIVILEGED_PRINCIPAL
+ "," + UNPRIVILEGED_PRINCIPAL
);
185 * Builds an ACL that will restrict the users who can issue commands to endpoints on the UI
186 * which are meant only for administrators.
188 public static AccessControlList
buildAdminAcl(Configuration conf
) {
189 final String userGroups
= conf
.get(HttpServer
.HTTP_SPNEGO_AUTHENTICATION_ADMIN_USERS_KEY
, null);
190 final String adminGroups
= conf
.get(
191 HttpServer
.HTTP_SPNEGO_AUTHENTICATION_ADMIN_GROUPS_KEY
, null);
192 if (userGroups
== null && adminGroups
== null) {
193 // Backwards compatibility - if the user doesn't have anything set, allow all users in.
194 return new AccessControlList("*", null);
196 return new AccessControlList(userGroups
, adminGroups
);
200 public void testProxyAllowed() throws Exception
{
201 testProxy(WHEEL_PRINCIPAL
, PRIVILEGED_PRINCIPAL
, HttpURLConnection
.HTTP_OK
, null);
205 public void testProxyDisallowedForUnprivileged() throws Exception
{
206 testProxy(WHEEL_PRINCIPAL
, UNPRIVILEGED_PRINCIPAL
, HttpURLConnection
.HTTP_FORBIDDEN
, "403 User unprivileged is unauthorized to access this page.");
210 public void testProxyDisallowedForNotSudoAble() throws Exception
{
211 testProxy(WHEEL_PRINCIPAL
, PRIVILEGED2_PRINCIPAL
, HttpURLConnection
.HTTP_FORBIDDEN
, "403 Forbidden");
214 public void testProxy(String clientPrincipal
, String doAs
, int responseCode
, String statusLine
) throws Exception
{
215 // Create the subject for the client
216 final Subject clientSubject
= JaasKrbUtil
.loginUsingKeytab(WHEEL_PRINCIPAL
, wheelKeytab
);
217 final Set
<Principal
> clientPrincipals
= clientSubject
.getPrincipals();
218 // Make sure the subject has a principal
219 assertFalse(clientPrincipals
.isEmpty());
221 // Get a TGT for the subject (might have many, different encryption types). The first should
222 // be the default encryption type.
223 Set
<KerberosTicket
> privateCredentials
=
224 clientSubject
.getPrivateCredentials(KerberosTicket
.class);
225 assertFalse(privateCredentials
.isEmpty());
226 KerberosTicket tgt
= privateCredentials
.iterator().next();
229 // The name of the principal
230 final String principalName
= clientPrincipals
.iterator().next().getName();
232 // Run this code, logged in as the subject (the client)
233 HttpResponse resp
= Subject
.doAs(clientSubject
, new PrivilegedExceptionAction
<HttpResponse
>() {
235 public HttpResponse
run() throws Exception
{
236 // Logs in with Kerberos via GSS
237 GSSManager gssManager
= GSSManager
.getInstance();
238 // jGSS Kerberos login constant
239 Oid oid
= new Oid("1.2.840.113554.1.2.2");
240 GSSName gssClient
= gssManager
.createName(principalName
, GSSName
.NT_USER_NAME
);
241 GSSCredential credential
= gssManager
.createCredential(gssClient
,
242 GSSCredential
.DEFAULT_LIFETIME
, oid
, GSSCredential
.INITIATE_ONLY
);
244 HttpClientContext context
= HttpClientContext
.create();
245 Lookup
<AuthSchemeProvider
> authRegistry
= RegistryBuilder
.<AuthSchemeProvider
>create()
246 .register(AuthSchemes
.SPNEGO
, new SPNegoSchemeFactory(true, true))
249 HttpClient client
= HttpClients
.custom().setDefaultAuthSchemeRegistry(authRegistry
)
251 BasicCredentialsProvider credentialsProvider
= new BasicCredentialsProvider();
252 credentialsProvider
.setCredentials(AuthScope
.ANY
, new KerberosCredentials(credential
));
254 URL url
= new URL(getServerURL(server
), "/echo?doAs=" + doAs
+ "&a=b");
255 context
.setTargetHost(new HttpHost(url
.getHost(), url
.getPort()));
256 context
.setCredentialsProvider(credentialsProvider
);
257 context
.setAuthSchemeRegistry(authRegistry
);
259 HttpGet get
= new HttpGet(url
.toURI());
260 return client
.execute(get
, context
);
265 assertEquals(responseCode
, resp
.getStatusLine().getStatusCode());
266 if (responseCode
== HttpURLConnection
.HTTP_OK
) {
267 assertTrue(EntityUtils
.toString(resp
.getEntity()).trim().contains("a:b"));
269 assertTrue(resp
.getStatusLine().toString().contains(statusLine
) ||
270 EntityUtils
.toString(resp
.getEntity()).contains(statusLine
));