You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2016/06/12 08:07:58 UTC

[11/13] calcite git commit: [CALCITE-1282] Adds an API method to set extra allowed Kerberos realms

[CALCITE-1282] Adds an API method to set extra allowed Kerberos realms

Our use of Jetty resulted in only the server's realm to be
allowed for SPNEGO authentication. We should expose the
configuration for downstream systems to set the allowed realms.


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/f68ae55c
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/f68ae55c
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/f68ae55c

Branch: refs/heads/master
Commit: f68ae55ca3eb9241a47d956a49a4b407ce40a565
Parents: b3f3916
Author: Josh Elser <el...@apache.org>
Authored: Thu Jun 9 18:45:20 2016 -0400
Committer: Julian Hyde <jh...@apache.org>
Committed: Sun Jun 12 01:06:58 2016 -0700

----------------------------------------------------------------------
 .../calcite/avatica/server/HttpServer.java      |  62 +++++++-
 .../avatica/server/HttpServerBuilderTest.java   | 146 +++++++++++++++++++
 2 files changed, 205 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/f68ae55c/avatica/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
----------------------------------------------------------------------
diff --git a/avatica/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java b/avatica/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
index b047137..3050f72 100644
--- a/avatica/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
+++ b/avatica/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
@@ -242,8 +242,24 @@ public class HttpServer {
     PropertyBasedSpnegoLoginService spnegoLoginService =
         new PropertyBasedSpnegoLoginService(realm, principal);
 
+    // Roles are "realms" for Kerberos/SPNEGO
+    final String[] allowedRealms = getAllowedRealms(realm, config);
+
     return configureCommonAuthentication(server, connector, config, Constraint.__SPNEGO_AUTH,
-        new String[] {realm}, new SpnegoAuthenticator(), realm, spnegoLoginService);
+        allowedRealms, new SpnegoAuthenticator(), realm, spnegoLoginService);
+  }
+
+  protected String[] getAllowedRealms(String serverRealm, AvaticaServerConfiguration config) {
+    // Roles are "realms" for Kerberos/SPNEGO
+    String[] allowedRealms = new String[] {serverRealm};
+    // By default, only the server's realm is allowed, but other realms can also be allowed.
+    if (null != config.getAllowedRoles()) {
+      allowedRealms = new String[config.getAllowedRoles().length + 1];
+      allowedRealms[0] = serverRealm;
+      System.arraycopy(config.getAllowedRoles(), 0, allowedRealms, 1,
+          config.getAllowedRoles().length);
+    }
+    return allowedRealms;
   }
 
   protected ConstraintSecurityHandler configureBasicAuthentication(Server server,
@@ -315,6 +331,10 @@ public class HttpServer {
     return connector;
   }
 
+  protected AvaticaServerConfiguration getConfig() {
+    return this.config;
+  }
+
   public void stop() {
     if (server == null) {
       throw new RuntimeException("Server is already stopped");
@@ -416,13 +436,29 @@ public class HttpServer {
      * @return <code>this</code>
      */
     public Builder withSpnego(String principal) {
+      return withSpnego(principal, (String[]) null);
+    }
+
+    /**
+     * Configures the server to use SPNEGO authentication. This method requires that the
+     * <code>principal</code> contains the Kerberos realm. Invoking this method overrides any
+     * previous call which configures authentication. Invoking this method overrides any previous
+     * call which configures authentication. By default, only principals from the server's realm are
+     * permitted, but additional realms can be allowed using <code>additionalAllowedRealms</code>.
+     *
+     * @param principal A kerberos principal with the realm required.
+     * @param additionalAllowedRealms Any additional realms, other than the server's realm, which
+     *    should be allowed to authenticate against the server. Can be null.
+     * @return <code>this</code>
+     */
+    public Builder withSpnego(String principal, String[] additionalAllowedRealms) {
       int index = Objects.requireNonNull(principal).lastIndexOf('@');
       if (-1 == index) {
         throw new IllegalArgumentException("Could not find '@' symbol in '" + principal
             + "' to parse the Kerberos realm from the principal");
       }
       final String realm = principal.substring(index + 1);
-      return withSpnego(principal, realm);
+      return withSpnego(principal, realm, additionalAllowedRealms);
     }
 
     /**
@@ -437,9 +473,28 @@ public class HttpServer {
      * @return <code>this</code>
      */
     public Builder withSpnego(String principal, String realm) {
+      return this.withSpnego(principal, realm, null);
+    }
+
+    /**
+     * Configures the server to use SPNEGO authentication. It is required that callers are logged
+     * in via Kerberos already or have provided the necessary configuration to automatically log
+     * in via JAAS (using the <code>java.security.auth.login.config</code> system property) before
+     * starting the {@link HttpServer}. Invoking this method overrides any previous call which
+     * configures authentication. By default, only principals from the server's realm are permitted,
+     * but additional realms can be allowed using <code>additionalAllowedRealms</code>.
+     *
+     * @param principal The kerberos principal
+     * @param realm The kerberos realm
+     * @param additionalAllowedRealms Any additional realms, other than the server's realm, which
+     *    should be allowed to authenticate against the server. Can be null.
+     * @return <code>this</code>
+     */
+    public Builder withSpnego(String principal, String realm, String[] additionalAllowedRealms) {
       this.authenticationType = AuthenticationType.SPNEGO;
       this.kerberosPrincipal = Objects.requireNonNull(principal);
       this.kerberosRealm = Objects.requireNonNull(realm);
+      this.loginServiceAllowedRoles = additionalAllowedRealms;
       return this;
     }
 
@@ -568,6 +623,7 @@ public class HttpServer {
     private AvaticaServerConfiguration buildSpnegoConfiguration(Builder b) {
       final String principal = b.kerberosPrincipal;
       final String realm = b.kerberosRealm;
+      final String[] additionalAllowedRealms = b.loginServiceAllowedRoles;
       final DoAsRemoteUserCallback callback = b.remoteUserCallback;
       return new AvaticaServerConfiguration() {
 
@@ -593,7 +649,7 @@ public class HttpServer {
         }
 
         @Override public String[] getAllowedRoles() {
-          return null;
+          return additionalAllowedRealms;
         }
 
         @Override public String getHashLoginServiceRealm() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/f68ae55c/avatica/server/src/test/java/org/apache/calcite/avatica/server/HttpServerBuilderTest.java
----------------------------------------------------------------------
diff --git a/avatica/server/src/test/java/org/apache/calcite/avatica/server/HttpServerBuilderTest.java b/avatica/server/src/test/java/org/apache/calcite/avatica/server/HttpServerBuilderTest.java
new file mode 100644
index 0000000..41bb88b
--- /dev/null
+++ b/avatica/server/src/test/java/org/apache/calcite/avatica/server/HttpServerBuilderTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.calcite.avatica.server;
+
+import org.apache.calcite.avatica.remote.Driver.Serialization;
+import org.apache.calcite.avatica.remote.Service;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Test class for {@link HttpServer}.
+ */
+public class HttpServerBuilderTest {
+
+  @Test public void extraAllowedRolesConfigured() {
+    final String[] extraRoles = new String[] {"BAR.COM"};
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertArrayEquals(extraRoles, server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM", "BAR.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+
+  @Test public void lotsOfExtraRoles() {
+    final String[] extraRoles = new String[] {"BAR.COM", "BAZ.COM", "FOO.COM"};
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertArrayEquals(extraRoles, server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM", "BAR.COM", "BAZ.COM", "FOO.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+
+  @Test public void nullExtraRoles() {
+    final String[] extraRoles = null;
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertNull(server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+
+  @Test public void emptyExtraRoles() {
+    final String[] extraRoles = new String[0];
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertArrayEquals(extraRoles, server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+
+  @Test public void extraAllowedRolesConfiguredWithExplitRealm() {
+    final String[] extraRoles = new String[] {"BAR.COM"};
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", "EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertArrayEquals(extraRoles, server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM", "BAR.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+
+  @Test public void lotsOfExtraRolesWithExplitRealm() {
+    final String[] extraRoles = new String[] {"BAR.COM", "BAZ.COM", "FOO.COM"};
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", "EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertArrayEquals(extraRoles, server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM", "BAR.COM", "BAZ.COM", "FOO.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+
+  @Test public void nullExtraRolesWithExplitRealm() {
+    final String[] extraRoles = null;
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", "EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertNull(server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+
+  @Test public void emptyExtraRolesWithExplitRealm() {
+    final String[] extraRoles = new String[0];
+    final Service mockService = Mockito.mock(Service.class);
+    HttpServer server = new HttpServer.Builder()
+        .withSpnego("HTTP/localhost.localdomain@EXAMPLE.COM", "EXAMPLE.COM", extraRoles)
+        .withHandler(mockService, Serialization.JSON)
+        .build();
+
+    assertArrayEquals(extraRoles, server.getConfig().getAllowedRoles());
+
+    assertArrayEquals(new String[] {"EXAMPLE.COM"},
+        server.getAllowedRealms("EXAMPLE.COM", server.getConfig()));
+  }
+}
+
+// End HttpServerBuilderTest.java