org.apache.hadoop.hbase.http.TestSpnegoHttpServer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.http.TestSpnegoHttpServer.java

Source

/*
 * 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();
    }
}