This is an automated email from the ASF dual-hosted git repository.

elserj pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2 by this push:
     new 8eb2f67  HBASE-24252 Implement proxyuser/doAs mechanism for hbase-http
8eb2f67 is described below

commit 8eb2f6752c720b0bad0cc29f981d9a60a00a5cb3
Author: Josh Elser <els...@apache.org>
AuthorDate: Mon Apr 27 13:05:53 2020 -0400

    HBASE-24252 Implement proxyuser/doAs mechanism for hbase-http
    
    copy ProxyUserAuthenticationFilter from Hadoop
    add hbase.security.authentication.spnego.kerberos.proxyuser.enable 
parameter (default false)
    wire ProxyUserAuthenticationFilter into HttpServer
    
    Signed-off-by: Sean Busbey <bus...@apache.org>
    Signed-off-by: Josh Elser <els...@apache.org>
---
 .../org/apache/hadoop/hbase/http/HttpServer.java   |  19 +-
 .../hbase/http/ProxyUserAuthenticationFilter.java  | 219 +++++++++++++++
 .../hbase/http/HttpServerFunctionalTest.java       |  11 +
 .../hbase/http/TestProxyUserSpnegoHttpServer.java  | 295 +++++++++++++++++++++
 4 files changed, 543 insertions(+), 1 deletion(-)

diff --git 
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java 
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
index 887380e..d3176ca 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
@@ -61,6 +61,7 @@ import org.apache.hadoop.security.SecurityUtil;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
 import org.apache.hadoop.security.authorize.AccessControlList;
+import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.util.Shell;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
@@ -127,6 +128,10 @@ public class HttpServer implements FilterContainer {
   static final String HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX = 
"kerberos.name.rules";
   public static final String HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_KEY =
       HTTP_SPNEGO_AUTHENTICATION_PREFIX + 
HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX;
+  static final String HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_SUFFIX = 
"kerberos.proxyuser.enable";
+  public static final String HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_KEY =
+      HTTP_SPNEGO_AUTHENTICATION_PREFIX + 
HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_SUFFIX;
+  public static final boolean  
HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_DEFAULT = false;
   static final String HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX =
       "signature.secret.file";
   public static final String HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_KEY =
@@ -145,6 +150,7 @@ public class HttpServer implements FilterContainer {
   public static final String ADMINS_ACL = "admins.acl";
   public static final String BIND_ADDRESS = "bind.address";
   public static final String SPNEGO_FILTER = "SpnegoFilter";
+  public static final String SPNEGO_PROXYUSER_FILTER = "SpnegoProxyUserFilter";
   public static final String NO_CACHE_FILTER = "NoCacheFilter";
   public static final String APP_DIR = "webapps";
 
@@ -1026,7 +1032,18 @@ public class HttpServer implements FilterContainer {
           + "to enable SPNEGO/Kerberos authentication for the Web UI");
     }
 
-    addGlobalFilter(SPNEGO_FILTER, AuthenticationFilter.class.getName(), 
params);
+    if (conf.getBoolean(HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_KEY,
+        HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_DEFAULT)) {
+        //Copy/rename standard hadoop proxyuser settings to filter
+        for(Map.Entry<String, String> proxyEntry :
+            
conf.getPropsWithPrefix(ProxyUsers.CONF_HADOOP_PROXYUSER).entrySet()) {
+            params.put(ProxyUserAuthenticationFilter.PROXYUSER_PREFIX + 
proxyEntry.getKey(),
+                proxyEntry.getValue());
+        }
+        addGlobalFilter(SPNEGO_PROXYUSER_FILTER, 
ProxyUserAuthenticationFilter.class.getName(), params);
+    } else {
+        addGlobalFilter(SPNEGO_FILTER, AuthenticationFilter.class.getName(), 
params);
+    }
   }
 
   /**
diff --git 
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProxyUserAuthenticationFilter.java
 
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProxyUserAuthenticationFilter.java
new file mode 100644
index 0000000..5fb17c9
--- /dev/null
+++ 
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProxyUserAuthenticationFilter.java
@@ -0,0 +1,219 @@
+/*
+ * 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 org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.ProxyUsers;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+import org.apache.hadoop.util.HttpExceptionUtils;
+import org.apache.hadoop.util.StringUtils;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.yetus.audience.InterfaceStability;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This file has been copied directly (changing only the package name and and 
the ASF license
+ * text format, and adding the Yetus annotations) from Hadoop, as the Hadoop 
version that HBase
+ * depends on doesn't have it yet
+ * (as of 2020 Apr 24, there is no Hadoop release that has it either).
+ *
+ * Hadoop version:
+ * unreleased, master branch commit 4ea6c2f457496461afc63f38ef4cef3ab0efce49
+ *
+ * Haddop path:
+ * 
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/authentication/
+ * server/ProxyUserAuthenticationFilter.java
+ *
+ * AuthenticationFilter which adds support to perform operations
+ * using end user instead of proxy user. Fetches the end user from
+ * doAs Query Parameter.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public class ProxyUserAuthenticationFilter extends AuthenticationFilter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(
+      ProxyUserAuthenticationFilter.class);
+
+  private static final String DO_AS = "doas";
+  public static final String PROXYUSER_PREFIX = "proxyuser";
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+    Configuration conf = getProxyuserConfiguration(filterConfig);
+    ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
+    super.init(filterConfig);
+  }
+
+  @Override
+  protected void doFilter(FilterChain filterChain, HttpServletRequest request,
+      HttpServletResponse response) throws IOException, ServletException {
+    final HttpServletRequest lowerCaseRequest = toLowerCase(request);
+    String doAsUser = lowerCaseRequest.getParameter(DO_AS);
+
+    if (doAsUser != null && !doAsUser.equals(request.getRemoteUser())) {
+      LOG.debug("doAsUser = {}, RemoteUser = {} , RemoteAddress = {} ",
+          doAsUser, request.getRemoteUser(), request.getRemoteAddr());
+      UserGroupInformation requestUgi = (request.getUserPrincipal() != null) ?
+          UserGroupInformation.createRemoteUser(request.getRemoteUser())
+          : null;
+      if (requestUgi != null) {
+        requestUgi = UserGroupInformation.createProxyUser(doAsUser,
+            requestUgi);
+        try {
+          ProxyUsers.authorize(requestUgi, request.getRemoteAddr());
+
+          final UserGroupInformation ugiF = requestUgi;
+          request = new HttpServletRequestWrapper(request) {
+            @Override
+            public String getRemoteUser() {
+              return ugiF.getShortUserName();
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+              return new Principal() {
+                @Override
+                public String getName() {
+                  return ugiF.getUserName();
+                }
+              };
+            }
+          };
+          LOG.debug("Proxy user Authentication successful");
+        } catch (AuthorizationException ex) {
+          HttpExceptionUtils.createServletExceptionResponse(response,
+              HttpServletResponse.SC_FORBIDDEN, ex);
+          LOG.warn("Proxy user Authentication exception", ex);
+          return;
+        }
+      }
+    }
+    super.doFilter(filterChain, request, response);
+  }
+
+  protected Configuration getProxyuserConfiguration(FilterConfig filterConfig)
+      throws ServletException {
+    Configuration conf = new Configuration(false);
+    Enumeration<?> names = filterConfig.getInitParameterNames();
+    while (names.hasMoreElements()) {
+      String name = (String) names.nextElement();
+      if (name.startsWith(PROXYUSER_PREFIX + ".")) {
+        String value = filterConfig.getInitParameter(name);
+        conf.set(name, value);
+      }
+    }
+    return conf;
+  }
+
+  static boolean containsUpperCase(final Iterable<String> strings) {
+    for(String s : strings) {
+      for(int i = 0; i < s.length(); i++) {
+        if (Character.isUpperCase(s.charAt(i))) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  public static HttpServletRequest toLowerCase(
+      final HttpServletRequest request) {
+    @SuppressWarnings("unchecked")
+    final Map<String, String[]> original = (Map<String, String[]>)
+        request.getParameterMap();
+    if (!containsUpperCase(original.keySet())) {
+      return request;
+    }
+
+    final Map<String, List<String>> m = new HashMap<String, List<String>>();
+    for (Map.Entry<String, String[]> entry : original.entrySet()) {
+      final String key = StringUtils.toLowerCase(entry.getKey());
+      List<String> strings = m.get(key);
+      if (strings == null) {
+        strings = new ArrayList<String>();
+        m.put(key, strings);
+      }
+      for (String v : entry.getValue()) {
+        strings.add(v);
+      }
+    }
+
+    return new HttpServletRequestWrapper(request) {
+      private Map<String, String[]> parameters = null;
+
+      @Override
+      public Map<String, String[]> getParameterMap() {
+        if (parameters == null) {
+          parameters = new HashMap<String, String[]>();
+          for (Map.Entry<String, List<String>> entry : m.entrySet()) {
+            final List<String> a = entry.getValue();
+            parameters.put(entry.getKey(), a.toArray(new String[a.size()]));
+          }
+        }
+        return parameters;
+      }
+
+      @Override
+      public String getParameter(String name) {
+        final List<String> a = m.get(name);
+        return a == null ? null : a.get(0);
+      }
+
+      @Override
+      public String[] getParameterValues(String name) {
+        return getParameterMap().get(name);
+      }
+
+      @Override
+      public Enumeration<String> getParameterNames() {
+        final Iterator<String> i = m.keySet().iterator();
+        return new Enumeration<String>() {
+          @Override
+          public boolean hasMoreElements() {
+            return i.hasNext();
+          }
+
+          @Override
+          public String nextElement() {
+            return i.next();
+          }
+        };
+      }
+    };
+  }
+
+}
diff --git 
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
 
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
index a854690..1be7297 100644
--- 
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
+++ 
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/HttpServerFunctionalTest.java
@@ -102,11 +102,22 @@ public class HttpServerFunctionalTest extends Assert {
   }
 
   public static HttpServer createTestServerWithSecurity(Configuration conf) 
throws IOException {
+      prepareTestWebapp();
+      return 
localServerBuilder(TEST).setFindPort(true).setConf(conf).setSecurityEnabled(true)
+          // InfoServer normally sets these for us
+          
.setUsernameConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY)
+          .setKeytabConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY)
+          .build();
+    }
+
+  public static HttpServer createTestServerWithSecurityAndAcl(Configuration 
conf, AccessControlList acl) throws IOException {
     prepareTestWebapp();
     return 
localServerBuilder(TEST).setFindPort(true).setConf(conf).setSecurityEnabled(true)
         // InfoServer normally sets these for us
         
.setUsernameConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY)
         .setKeytabConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY)
+        .setSecurityEnabled(true)
+        .setACL(acl)
         .build();
   }
 
diff --git 
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestProxyUserSpnegoHttpServer.java
 
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestProxyUserSpnegoHttpServer.java
new file mode 100644
index 0000000..2036b31
--- /dev/null
+++ 
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestProxyUserSpnegoHttpServer.java
@@ -0,0 +1,295 @@
+/**
+ * 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.File;
+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.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseCommonTestingUtility;
+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.hadoop.security.authorize.AccessControlList;
+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.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.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test class for SPNEGO Proxyuser authentication on the HttpServer. Uses 
Kerby's MiniKDC and Apache
+ * HttpComponents to verify that the doas= mechanicsm works, and that the 
proxyuser settings are
+ * observed
+ */
+@Category({MiscTests.class, SmallTests.class})
+public class TestProxyUserSpnegoHttpServer extends HttpServerFunctionalTest {
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+      HBaseClassTestRule.forClass(TestProxyUserSpnegoHttpServer.class);
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(TestProxyUserSpnegoHttpServer.class);
+  private static final String KDC_SERVER_HOST = "localhost";
+  private static final String WHEEL_PRINCIPAL = "wheel";
+  private static final String UNPRIVILEGED_PRINCIPAL = "unprivileged";
+  private static final String PRIVILEGED_PRINCIPAL = "privileged";
+  private static final String PRIVILEGED2_PRINCIPAL = "privileged2";
+
+  private static HttpServer server;
+  private static URL baseUrl;
+  private static SimpleKdcServer kdc;
+  private static File infoServerKeytab;
+  private static File wheelKeytab;
+  private static File unprivilegedKeytab;
+  private static File privilegedKeytab;
+  private static File privileged2Keytab;
+
+
+  @BeforeClass
+  public static void setupServer() throws Exception {
+    Configuration conf = new Configuration();
+    HBaseCommonTestingUtility htu = new HBaseCommonTestingUtility(conf);
+
+    final String serverPrincipal = "HTTP/" + KDC_SERVER_HOST;
+
+    kdc = buildMiniKdc();
+    kdc.start();
+    File keytabDir = new File(htu.getDataTestDir("keytabs").toString());
+    if (keytabDir.exists()) {
+      deleteRecursively(keytabDir);
+    }
+    keytabDir.mkdirs();
+
+    infoServerKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + 
".keytab");
+    wheelKeytab = new File(keytabDir, WHEEL_PRINCIPAL + ".keytab");
+    unprivilegedKeytab = new File(keytabDir, UNPRIVILEGED_PRINCIPAL + 
".keytab");
+    privilegedKeytab = new File(keytabDir, PRIVILEGED_PRINCIPAL + ".keytab");
+    privileged2Keytab = new File(keytabDir, PRIVILEGED2_PRINCIPAL + ".keytab");
+
+    setupUser(kdc, wheelKeytab, WHEEL_PRINCIPAL);
+    setupUser(kdc, unprivilegedKeytab, UNPRIVILEGED_PRINCIPAL);
+    setupUser(kdc, privilegedKeytab, PRIVILEGED_PRINCIPAL);
+    setupUser(kdc, privileged2Keytab, PRIVILEGED2_PRINCIPAL);
+
+    setupUser(kdc, infoServerKeytab, serverPrincipal);
+
+    buildSpnegoConfiguration(conf, serverPrincipal, infoServerKeytab);
+    AccessControlList acl = buildAdminAcl(conf);
+
+    server = createTestServerWithSecurityAndAcl(conf, acl);
+    server.addPrivilegedServlet("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, 
TestProxyUserSpnegoHttpServer.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;
+  }
+
+  protected static Configuration buildSpnegoConfiguration(Configuration conf, 
String serverPrincipal,
+      File serverKeytab) {
+    KerberosName.setRules("DEFAULT");
+
+    conf.setInt(HttpServer.HTTP_MAX_THREADS, TestHttpServer.MAX_THREADS);
+
+    // 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());
+
+    conf.set(HttpServer.HTTP_SPNEGO_AUTHENTICATION_ADMIN_USERS_KEY, 
PRIVILEGED_PRINCIPAL);
+    conf.set(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PROXYUSER_ENABLE_KEY, 
"true");
+    conf.set("hadoop.security.authorization", "true");
+
+    conf.set("hadoop.proxyuser.wheel.hosts", "*");
+    conf.set("hadoop.proxyuser.wheel.users", PRIVILEGED_PRINCIPAL + "," + 
UNPRIVILEGED_PRINCIPAL);
+    return conf;
+  }
+
+  /**
+   * Builds an ACL that will restrict the users who can issue commands to 
endpoints on the UI
+   * which are meant only for administrators.
+   */
+  public static AccessControlList buildAdminAcl(Configuration conf) {
+    final String userGroups = 
conf.get(HttpServer.HTTP_SPNEGO_AUTHENTICATION_ADMIN_USERS_KEY, null);
+    final String adminGroups = conf.get(
+        HttpServer.HTTP_SPNEGO_AUTHENTICATION_ADMIN_GROUPS_KEY, null);
+    if (userGroups == null && adminGroups == null) {
+      // Backwards compatibility - if the user doesn't have anything set, 
allow all users in.
+      return new AccessControlList("*", null);
+    }
+    return new AccessControlList(userGroups, adminGroups);
+  }
+
+  @Test
+  public void testProxyAllowed() throws Exception {
+      testProxy(WHEEL_PRINCIPAL, PRIVILEGED_PRINCIPAL, 
HttpURLConnection.HTTP_OK, null);
+  }
+
+  @Test
+  public void testProxyDisallowedForUnprivileged() throws Exception {
+      testProxy(WHEEL_PRINCIPAL, UNPRIVILEGED_PRINCIPAL, 
HttpURLConnection.HTTP_FORBIDDEN, "403 User unprivileged is unauthorized to 
access this page.");
+  }
+
+  @Test
+  public void testProxyDisallowedForNotSudoAble() throws Exception {
+      testProxy(WHEEL_PRINCIPAL, PRIVILEGED2_PRINCIPAL, 
HttpURLConnection.HTTP_FORBIDDEN, "403 Forbidden");
+  }
+
+  public void testProxy(String clientPrincipal, String doAs, int responseCode, 
String statusLine) throws Exception {
+    // Create the subject for the client
+    final Subject clientSubject = 
JaasKrbUtil.loginUsingKeytab(WHEEL_PRINCIPAL, wheelKeytab);
+    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?doAs=" + doAs + 
"&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(responseCode, resp.getStatusLine().getStatusCode());
+    if(responseCode == HttpURLConnection.HTTP_OK) {
+        
assertTrue(EntityUtils.toString(resp.getEntity()).trim().contains("a:b"));
+    } else {
+        assertTrue(resp.getStatusLine().toString().contains(statusLine));
+    }
+  }
+
+}

Reply via email to