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