1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.hadoop.hbase.http;
18
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStreamReader;
23 import java.net.HttpURLConnection;
24 import java.net.URL;
25 import java.security.Principal;
26 import java.security.PrivilegedExceptionAction;
27 import java.util.Set;
28
29 import javax.security.auth.Subject;
30 import javax.security.auth.kerberos.KerberosTicket;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.hbase.http.TestHttpServer.EchoServlet;
36 import org.apache.hadoop.hbase.http.resource.JerseyResource;
37 import org.apache.hadoop.hbase.testclassification.MiscTests;
38 import org.apache.hadoop.hbase.testclassification.SmallTests;
39 import org.apache.hadoop.security.authentication.util.KerberosName;
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.entity.ByteArrayEntity;
52 import org.apache.http.entity.ContentType;
53 import org.apache.http.impl.auth.SPNegoSchemeFactory;
54 import org.apache.http.impl.client.BasicCredentialsProvider;
55 import org.apache.http.impl.client.HttpClients;
56 import org.apache.http.util.EntityUtils;
57 import org.apache.kerby.kerberos.kerb.KrbException;
58 import org.apache.kerby.kerberos.kerb.client.JaasKrbUtil;
59 import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
60 import org.ietf.jgss.GSSCredential;
61 import org.ietf.jgss.GSSManager;
62 import org.ietf.jgss.GSSName;
63 import org.ietf.jgss.Oid;
64 import org.junit.AfterClass;
65 import org.junit.BeforeClass;
66 import org.junit.Test;
67 import org.junit.experimental.categories.Category;
68
69
70
71
72
73 @Category({MiscTests.class, SmallTests.class})
74 public class TestSpnegoHttpServer extends HttpServerFunctionalTest {
75 private static final Log LOG = LogFactory.getLog(TestSpnegoHttpServer.class);
76 private static final String KDC_SERVER_HOST = "localhost";
77 private static final String CLIENT_PRINCIPAL = "client";
78
79 private static HttpServer server;
80 private static URL baseUrl;
81 private static SimpleKdcServer kdc;
82 private static File infoServerKeytab;
83 private static File clientKeytab;
84
85 @BeforeClass
86 public static void setupServer() throws Exception {
87 final String serverPrincipal = "HTTP/" + KDC_SERVER_HOST;
88 final File target = new File(System.getProperty("user.dir"), "target");
89 assertTrue(target.exists());
90
91 kdc = buildMiniKdc();
92 kdc.start();
93
94 File keytabDir = new File(target, TestSpnegoHttpServer.class.getSimpleName()
95 + "_keytabs");
96 if (keytabDir.exists()) {
97 deleteRecursively(keytabDir);
98 }
99 keytabDir.mkdirs();
100
101 infoServerKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + ".keytab");
102 clientKeytab = new File(keytabDir, CLIENT_PRINCIPAL + ".keytab");
103
104 setupUser(kdc, clientKeytab, CLIENT_PRINCIPAL);
105 setupUser(kdc, infoServerKeytab, serverPrincipal);
106
107 Configuration conf = buildSpnegoConfiguration(serverPrincipal, infoServerKeytab);
108
109 server = createTestServerWithSecurity(conf);
110 server.addServlet("echo", "/echo", EchoServlet.class);
111 server.addJerseyResourcePackage(JerseyResource.class.getPackage().getName(), "/jersey/*");
112 server.start();
113 baseUrl = getServerURL(server);
114
115 LOG.info("HTTP server started: "+ baseUrl);
116 }
117
118 @AfterClass
119 public static void stopServer() throws Exception {
120 try {
121 if (null != server) {
122 server.stop();
123 }
124 } catch (Exception e) {
125 LOG.info("Failed to stop info server", e);
126 }
127 try {
128 if (null != kdc) {
129 kdc.stop();
130 }
131 } catch (Exception e) {
132 LOG.info("Failed to stop mini KDC", e);
133 }
134 }
135
136 private static void setupUser(SimpleKdcServer kdc, File keytab, String principal)
137 throws KrbException {
138 kdc.createPrincipal(principal);
139 kdc.exportPrincipal(principal, keytab);
140 }
141
142 private static SimpleKdcServer buildMiniKdc() throws Exception {
143 SimpleKdcServer kdc = new SimpleKdcServer();
144
145 final File target = new File(System.getProperty("user.dir"), "target");
146 File kdcDir = new File(target, TestSpnegoHttpServer.class.getSimpleName());
147 if (kdcDir.exists()) {
148 deleteRecursively(kdcDir);
149 }
150 kdcDir.mkdirs();
151 kdc.setWorkDir(kdcDir);
152
153 kdc.setKdcHost(KDC_SERVER_HOST);
154 int kdcPort = getFreePort();
155 kdc.setAllowTcp(true);
156 kdc.setAllowUdp(false);
157 kdc.setKdcTcpPort(kdcPort);
158
159 LOG.info("Starting KDC server at " + KDC_SERVER_HOST + ":" + kdcPort);
160
161 kdc.init();
162
163 return kdc;
164 }
165
166 private static Configuration buildSpnegoConfiguration(String serverPrincipal, File
167 serverKeytab) {
168 Configuration conf = new Configuration();
169 KerberosName.setRules("DEFAULT");
170
171 conf.setInt(HttpServer.HTTP_MAX_THREADS, 10);
172
173
174 conf.set("hbase.security.authentication", "kerberos");
175 conf.set(HttpServer.HTTP_UI_AUTHENTICATION, "kerberos");
176 conf.set(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY, serverPrincipal);
177 conf.set(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY, serverKeytab.getAbsolutePath());
178
179 return conf;
180 }
181
182 @Test
183 public void testUnauthorizedClientsDisallowed() throws IOException {
184 URL url = new URL(getServerURL(server), "/echo?a=b");
185 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
186 assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
187 }
188
189 @Test
190 public void testAllowedClient() throws Exception {
191
192 final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(CLIENT_PRINCIPAL, clientKeytab);
193 final Set<Principal> clientPrincipals = clientSubject.getPrincipals();
194
195 assertFalse(clientPrincipals.isEmpty());
196
197
198
199 Set<KerberosTicket> privateCredentials =
200 clientSubject.getPrivateCredentials(KerberosTicket.class);
201 assertFalse(privateCredentials.isEmpty());
202 KerberosTicket tgt = privateCredentials.iterator().next();
203 assertNotNull(tgt);
204
205
206 final String principalName = clientPrincipals.iterator().next().getName();
207
208
209 HttpResponse resp = Subject.doAs(clientSubject,
210 new PrivilegedExceptionAction<HttpResponse>() {
211 @Override
212 public HttpResponse run() throws Exception {
213
214 GSSManager gssManager = GSSManager.getInstance();
215
216 Oid oid = new Oid("1.2.840.113554.1.2.2");
217 GSSName gssClient = gssManager.createName(principalName, GSSName.NT_USER_NAME);
218 GSSCredential credential = gssManager.createCredential(gssClient,
219 GSSCredential.DEFAULT_LIFETIME, oid, GSSCredential.INITIATE_ONLY);
220
221 HttpClientContext context = HttpClientContext.create();
222 Lookup<AuthSchemeProvider> authRegistry = RegistryBuilder.<AuthSchemeProvider>create()
223 .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true, true))
224 .build();
225
226 HttpClient client = HttpClients.custom().setDefaultAuthSchemeRegistry(authRegistry).build();
227 BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
228 credentialsProvider.setCredentials(AuthScope.ANY, new KerberosCredentials(credential));
229
230 URL url = new URL(getServerURL(server), "/echo?a=b");
231 context.setTargetHost(new HttpHost(url.getHost(), url.getPort()));
232 context.setCredentialsProvider(credentialsProvider);
233 context.setAuthSchemeRegistry(authRegistry);
234
235 HttpGet get = new HttpGet(url.toURI());
236 return client.execute(get, context);
237 }
238 });
239
240 assertNotNull(resp);
241 assertEquals(HttpURLConnection.HTTP_OK, resp.getStatusLine().getStatusCode());
242 assertEquals("a:b", EntityUtils.toString(resp.getEntity()).trim());
243 }
244
245 @Test(expected = IllegalArgumentException.class)
246 public void testMissingConfigurationThrowsException() throws Exception {
247 Configuration conf = new Configuration();
248 conf.setInt(HttpServer.HTTP_MAX_THREADS, 10);
249
250 conf.set("hbase.security.authentication", "kerberos");
251
252
253 HttpServer customServer = createTestServerWithSecurity(conf);
254 customServer.addServlet("echo", "/echo", EchoServlet.class);
255 customServer.addJerseyResourcePackage(JerseyResource.class.getPackage().getName(), "/jersey/*");
256 customServer.start();
257 }
258 }