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

elserj pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite-avatica.git


The following commit(s) were added to refs/heads/master by this push:
     new c3a9192  [CALCITE-4152] Upgrade Avatica to use the configurable SPNEGO 
Jetty implementation
c3a9192 is described below

commit c3a9192347b4354337a906838499ff25236d63bc
Author: Josh Elser <[email protected]>
AuthorDate: Thu Dec 31 23:28:15 2020 -0500

    [CALCITE-4152] Upgrade Avatica to use the configurable SPNEGO Jetty 
implementation
    
    Jetty has deprecated the previously-used version of SPNEGO login code.
    This change requires a few other changes to adopt:
    
    1. Removal of automatic server login via JAAS (Jetty removed this and
        expects explicit logins for the server).
    2. Separation of Authentication and Authorization (we're required to
        use a LoginService for authz to use the new SPNEGO authentication).
    
    For the benefit of making this change, we automatically inherit the
    Jetty Session logic which can skip SPNEGO authentication for the 2nd
    to Nth call to Avatica. For a "workload" which previously took N HTTP calls
    to Avatica to perform, this can now be done in (N/2)+1 HTTP calls
    which, for average Avatica calls, results in a nearly 2x speed-up.
    
    Jetty Sessions will cause a JSESSIONID cookie to be sent back on the
    successful SPNEGO authentication handshake. As long as the client
    resubmits this cookie for subsequent requests, the identity of the
    client is kept intact.
    
    To test this more easily, this change also includes updates to the
    Avatica StandaloneServer, which more easily enables setup of Avatica
    against any database (e.g. hsqldb with the SCOTT dataset).
---
 gradle.properties                                  |   2 +-
 .../avatica/server/AvaticaServerConfiguration.java |  20 ++
 .../calcite/avatica/server/AvaticaUserStore.java   |  45 ++++
 .../apache/calcite/avatica/server/HttpServer.java  | 134 ++++++++---
 .../apache/calcite/avatica/AvaticaSpnegoTest.java  |  10 +-
 .../org/apache/calcite/avatica/SpnegoTestUtil.java |   3 +-
 .../avatica/server/CustomAuthHttpServerTest.java   |  20 +-
 ...ueryStringParameterRemoteUserExtractorTest.java |   8 +
 .../avatica/server/HttpServerBuilderTest.java      | 146 ------------
 .../server/HttpServerSpnegoWithJaasTest.java       | 255 ---------------------
 .../server/HttpServerSpnegoWithoutJaasTest.java    |   6 +-
 site/_docs/security.md                             |  50 +++-
 standalone-server/build.gradle.kts                 |   5 +-
 .../avatica/standalone/StandaloneServer.java       |  38 ++-
 .../src/main/resources/log4j.properties            |   4 +-
 15 files changed, 290 insertions(+), 456 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index ddfbbbf..5de965a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -69,7 +69,7 @@ httpcore.version=4.4.11
 jackson.version=2.10.0
 jcip-annotations.version=1.0-1
 jcommander.version=1.72
-jetty.version=9.4.42.v20210604
+jetty.version=9.4.44.v20210927
 junit.version=4.12
 kerby.version=1.1.1
 mockito.version=2.23.4
diff --git 
a/server/src/main/java/org/apache/calcite/avatica/server/AvaticaServerConfiguration.java
 
b/server/src/main/java/org/apache/calcite/avatica/server/AvaticaServerConfiguration.java
index 56860ec..f3ab986 100644
--- 
a/server/src/main/java/org/apache/calcite/avatica/server/AvaticaServerConfiguration.java
+++ 
b/server/src/main/java/org/apache/calcite/avatica/server/AvaticaServerConfiguration.java
@@ -18,6 +18,7 @@ package org.apache.calcite.avatica.server;
 
 import org.apache.calcite.avatica.remote.AuthenticationType;
 
+import java.io.File;
 import java.util.concurrent.Callable;
 
 /**
@@ -40,6 +41,18 @@ public interface AvaticaServerConfiguration {
   String getKerberosRealm();
 
   /**
+   * Returns the "primary" component of the Kerberos principal for the Avatica 
server,
+   * e.g. primary/instance@REALM
+   */
+  String getKerberosServiceName();
+
+  /**
+   * Returns the "instance" component of the Kerberos principal for the 
Avatica server,
+   * e.g. primary/instance@REALM
+   */
+  String getKerberosHostName();
+
+  /**
    * Returns the Kerberos principal that the Avatica server should log in as.
    *
    * @return A Kerberos principal, or null if not applicable.
@@ -47,6 +60,13 @@ public interface AvaticaServerConfiguration {
   String getKerberosPrincipal();
 
   /**
+   * Returns the file to the server's Kerberos keytab.
+   */
+  default File getKerberosKeytab() {
+    return null;
+  }
+
+  /**
    * Returns the array of allowed roles for login. Only applicable when
    * {@link #getAuthenticationType()} returns {@link AuthenticationType#BASIC} 
or
    * {@link AuthenticationType#DIGEST}.
diff --git 
a/server/src/main/java/org/apache/calcite/avatica/server/AvaticaUserStore.java 
b/server/src/main/java/org/apache/calcite/avatica/server/AvaticaUserStore.java
new file mode 100644
index 0000000..18104cf
--- /dev/null
+++ 
b/server/src/main/java/org/apache/calcite/avatica/server/AvaticaUserStore.java
@@ -0,0 +1,45 @@
+/*
+ * 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.eclipse.jetty.security.UserStore;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * Implementation of UserStore which creates users when they do not already 
exist.
+ */
+public class AvaticaUserStore extends UserStore {
+  private static final Credential USER_CREDENTIAL = 
Credential.getCredential("");
+
+  public static final String AVATICA_USER_ROLE = "avatica-user";
+
+  private static final String[] USER_ROLES = new String[] {AVATICA_USER_ROLE};
+
+  @Override public UserIdentity getUserIdentity(String userName) {
+    UserIdentity userId = super.getUserIdentity(userName);
+    if (userId != null) {
+      return userId;
+    }
+
+    // Do we need to be concerned about the recursion?
+    addUser(userName, USER_CREDENTIAL, USER_ROLES);
+    return getUserIdentity(userName);
+  }
+}
+
+// End AvaticaUserStore.java
diff --git 
a/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java 
b/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
index c094fc8..87dc73e 100644
--- a/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
+++ b/server/src/main/java/org/apache/calcite/avatica/server/HttpServer.java
@@ -23,11 +23,14 @@ import org.apache.calcite.avatica.remote.Service;
 import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
 
 import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.ConfigurableSpnegoLoginService;
 import org.eclipse.jetty.security.ConstraintMapping;
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.HashLoginService;
 import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.authentication.AuthorizationService;
 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import 
org.eclipse.jetty.security.authentication.ConfigurableSpnegoAuthenticator;
 import org.eclipse.jetty.security.authentication.DigestAuthenticator;
 import org.eclipse.jetty.server.AbstractConnectionFactory;
 import org.eclipse.jetty.server.Connector;
@@ -37,6 +40,8 @@ import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.DefaultHandler;
 import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.session.DefaultSessionIdManager;
+import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.util.security.Constraint;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -48,7 +53,10 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.security.Principal;
 import java.security.PrivilegedAction;
+import java.security.SecureRandom;
+import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -275,7 +283,10 @@ public class HttpServer {
     if (null != config) {
       ConstraintSecurityHandler securityHandler = getSecurityHandler();
       securityHandler.setHandler(handler);
-      avaticaHandler = securityHandler;
+      // SPNEGO requires a session
+      SessionHandler sessionHandler = new SessionHandler();
+      sessionHandler.setHandler(securityHandler);
+      avaticaHandler = sessionHandler;
     }
 
     handlerList.setHandlers(new Handler[] {avaticaHandler, new 
DefaultHandler()});
@@ -337,31 +348,45 @@ public class HttpServer {
   protected ConstraintSecurityHandler configureSpnego(Server server,
       AvaticaServerConfiguration config) {
     final String realm = Objects.requireNonNull(config.getKerberosRealm());
-    final String principal = 
Objects.requireNonNull(config.getKerberosPrincipal());
+
+    // DefaultSessionIdManager uses SecureRandom, but we can be explicit about 
that.
+    server.setSessionIdManager(new DefaultSessionIdManager(server, new 
SecureRandom()));
+
+    // We rely on SPNEGO to authenticate the users with valid Kerberos 
identities. We
+    // do not require a _specific_ Kerberos identity in order to authenticate 
with
+    // Avatica. AvaticaUserStore will assign the role "avatica-user" to every 
SPNEGO-authenticated
+    // user, and then ConfigurableSpnegoAuthenticator will check that role.
+    //
+    // This setup adds nothing but complexity to Avatica, but Jetty removed the
+    // functionality to not have this layer of indirection. It paves the way 
for
+    // flexibility in having "user" centric HTTP endpoints and "admin" centric
+    // HTTP endpoints which Avatica can authorize appropriately.
+    final AvaticaUserStore userStore = new AvaticaUserStore();
+    LOG.info("Instantiating HashLoginService with {}", realm);
+    // Passing the Kerberos Realm here was previously important, but is not 
critical any longer.
+    final HashLoginService authz = new HashLoginService(realm);
+    authz.setUserStore(userStore);
 
     // A customization of SpnegoLoginService to explicitly set the server's 
principal, otherwise
     // we would have to require a custom file to set the server's principal.
-    PropertyBasedSpnegoLoginService spnegoLoginService =
-        new PropertyBasedSpnegoLoginService(realm, principal);
-
-    // Roles are "realms" for Kerberos/SPNEGO
-    final String[] allowedRealms = getAllowedRealms(realm, config);
+    ConfigurableSpnegoLoginService spnegoLoginService =
+        new ConfigurableSpnegoLoginService(realm, 
AuthorizationService.from(authz, ""));
+    // Why? The Jetty unit test does it.
+    spnegoLoginService.addBean(authz);
+    spnegoLoginService.setServiceName(config.getKerberosServiceName());
+    spnegoLoginService.setHostName(config.getKerberosHostName());
+    spnegoLoginService.setKeyTabPath(config.getKerberosKeytab().toPath());
+
+    // The Authenticator independently validates what role(s) the authenticated
+    // user has and authorizes them to access the HTTP resources. We use 
"avatica-user"
+    // as the role to check.
+    final String[] allowedRealms = new String[] 
{AvaticaUserStore.AVATICA_USER_ROLE};
+
+    final ConfigurableSpnegoAuthenticator spnegoAuthn = new 
ConfigurableSpnegoAuthenticator();
+    spnegoAuthn.setAuthenticationDuration(Duration.ofMinutes(5));
 
     return configureCommonAuthentication(Constraint.__SPNEGO_AUTH,
-        allowedRealms, new AvaticaSpnegoAuthenticator(), 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;
+        allowedRealms, spnegoAuthn, realm, spnegoLoginService);
   }
 
   protected ConstraintSecurityHandler configureBasicAuthentication(Server 
server,
@@ -404,13 +429,12 @@ public class HttpServer {
     cm.setConstraint(constraint);
     cm.setPathSpec("/*");
 
-    ConstraintSecurityHandler sh = new ConstraintSecurityHandler();
-    sh.setAuthenticator(authenticator);
-    sh.setLoginService(loginService);
-    sh.setConstraintMappings(new ConstraintMapping[]{cm});
-    sh.setRealmName(realm);
+    ConstraintSecurityHandler securityHandler = new 
ConstraintSecurityHandler();
+    securityHandler.setAuthenticator(authenticator);
+    securityHandler.setLoginService(loginService);
+    securityHandler.setConstraintMappings(new ConstraintMapping[]{cm});
 
-    return sh;
+    return securityHandler;
   }
 
   /**
@@ -574,7 +598,9 @@ public class HttpServer {
      * @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>
+     * @deprecated Since 1.20.0, because {@code additionalAllowedRealms} is no 
longer considered.
      */
+    @Deprecated
     public Builder<T> withSpnego(String principal, String[] 
additionalAllowedRealms) {
       int index = Objects.requireNonNull(principal).lastIndexOf('@');
       if (-1 == index) {
@@ -613,12 +639,20 @@ public class HttpServer {
      * @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>
+     * @deprecated since 1.20.0 because {@code additionalAllowedRealms} is no 
longer considered.
      */
+    @Deprecated
     public Builder<T> withSpnego(String principal, String realm, String[] 
additionalAllowedRealms) {
       this.authenticationType = AuthenticationType.SPNEGO;
       this.kerberosPrincipal = Objects.requireNonNull(principal);
       this.kerberosRealm = Objects.requireNonNull(realm);
+      if (additionalAllowedRealms != null) {
+        LOG.warn("Avatica no longer support additionalAllowedRealms as the 
Jetty SPNEGO"
+            + " implementation does not adhere to it. All authenticateable 
realms are allowed: {}",
+            Arrays.toString(additionalAllowedRealms));
+      }
       this.loginServiceAllowedRoles = additionalAllowedRealms;
+
       return this;
     }
 
@@ -653,7 +687,7 @@ public class HttpServer {
      * @return <code>this</code>
      */
 
-    public Builder withRemoteUserExtractor(RemoteUserExtractor 
remoteUserExtractor) {
+    public Builder<T> withRemoteUserExtractor(RemoteUserExtractor 
remoteUserExtractor) {
       this.remoteUserExtractor = Objects.requireNonNull(remoteUserExtractor);
       return this;
     }
@@ -781,13 +815,8 @@ public class HttpServer {
         handler = buildHandler(this, serverConfig);
         break;
       case SPNEGO:
-        if (null != keytab) {
-          LOG.debug("Performing Kerberos login with {} as {}", keytab, 
kerberosPrincipal);
-          subject = loginViaKerberos(this);
-        } else {
-          LOG.debug("Not performing Kerberos login");
-          subject = null;
-        }
+        LOG.debug("Not performing Kerberos login, Jetty does this now");
+        subject = null;
         serverConfig = buildSpnegoConfiguration(this);
         handler = buildHandler(this, serverConfig);
         break;
@@ -854,7 +883,22 @@ public class HttpServer {
      */
     private AvaticaServerConfiguration buildSpnegoConfiguration(Builder b) {
       final String principal = b.kerberosPrincipal;
+      final int separatorIndex = principal.indexOf('/');
+      if (separatorIndex < 1) {
+        throw new RuntimeException("Expected principal to be of the form 
primary/instance"
+            + " but got " + principal);
+      }
+      final String primary = principal.substring(0, separatorIndex);
+      final int atSignIndex = principal.indexOf('@');
+      final String instance;
+      // Trim off the @REALM if it's present
+      if (atSignIndex == -1) {
+        instance = principal.substring(separatorIndex + 1);
+      } else {
+        instance = principal.substring(separatorIndex + 1, atSignIndex);
+      }
       final String realm = b.kerberosRealm;
+      final File keytab = b.keytab;
       final String[] additionalAllowedRealms = b.loginServiceAllowedRoles;
       final DoAsRemoteUserCallback callback = b.remoteUserCallback;
       final RemoteUserExtractor remoteUserExtractor = b.remoteUserExtractor;
@@ -872,6 +916,18 @@ public class HttpServer {
           return principal;
         }
 
+        @Override public String getKerberosServiceName() {
+          return primary;
+        }
+
+        @Override public String getKerberosHostName() {
+          return instance;
+        }
+
+        @Override public File getKerberosKeytab() {
+          return keytab;
+        }
+
         @Override public boolean supportsImpersonation() {
           return null != callback;
         }
@@ -933,6 +989,14 @@ public class HttpServer {
           return null;
         }
 
+        @Override public String getKerberosServiceName() {
+          return null;
+        }
+
+        @Override public String getKerberosHostName() {
+          return null;
+        }
+
         @Override public boolean supportsImpersonation() {
           return false;
         }
diff --git 
a/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java 
b/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java
index 0d1972f..66821c8 100644
--- a/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/AvaticaSpnegoTest.java
@@ -65,6 +65,12 @@ public class AvaticaSpnegoTest extends HttpBaseTest {
   private static boolean isKdcStarted = false;
 
   private static void setupKdc() throws Exception {
+    System.setProperty("sun.security.krb5.debug", "true");
+    System.setProperty("sun.security.jgss.debug", "true");
+    System.setProperty("sun.security.spnego.debug", "true");
+    System.setProperty("java.security.debug", "all");
+    System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
+
     if (isKdcStarted) {
       return;
     }
@@ -108,9 +114,9 @@ public class AvaticaSpnegoTest extends HttpBaseTest {
     clientConfig.setString(KrbConfigKey.DEFAULT_REALM, SpnegoTestUtil.REALM);
 
     // Kerby sets "java.security.krb5.conf" for us!
+    // useSubjectCredsOnly is only important when we expect JAAS to go 
"looking"
+    // for credentials on our behalf.
     System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
-    //System.setProperty("sun.security.spnego.debug", "true");
-    //System.setProperty("sun.security.krb5.debug", "true");
   }
 
   @AfterClass public static void stopKdc() throws KrbException {
diff --git 
a/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java 
b/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java
index 2c5c763..03857d8 100644
--- a/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java
+++ b/server/src/test/java/org/apache/calcite/avatica/SpnegoTestUtil.java
@@ -55,7 +55,8 @@ public class SpnegoTestUtil {
 
   public static final String REALM = "EXAMPLE.COM";
   public static final String KDC_HOST = "localhost";
-  public static final String CLIENT_PRINCIPAL = "client@" + REALM;
+  public static final String CLIENT_NAME = "client";
+  public static final String CLIENT_PRINCIPAL = CLIENT_NAME + "@" + REALM;
   public static final String SERVER_PRINCIPAL = "HTTP/" + KDC_HOST + "@" + 
REALM;
 
   private static final String TARGET_DIR_NAME = 
System.getProperty("target.dir", "target");
diff --git 
a/server/src/test/java/org/apache/calcite/avatica/server/CustomAuthHttpServerTest.java
 
b/server/src/test/java/org/apache/calcite/avatica/server/CustomAuthHttpServerTest.java
index 464da84..5a9ee03 100644
--- 
a/server/src/test/java/org/apache/calcite/avatica/server/CustomAuthHttpServerTest.java
+++ 
b/server/src/test/java/org/apache/calcite/avatica/server/CustomAuthHttpServerTest.java
@@ -22,7 +22,6 @@ import org.apache.calcite.avatica.remote.AuthenticationType;
 import org.apache.calcite.avatica.remote.Driver;
 import org.apache.calcite.avatica.remote.LocalService;
 
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.UserAuthentication;
 import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.Connector;
@@ -33,6 +32,7 @@ import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.handler.DefaultHandler;
 import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -208,7 +208,7 @@ public class CustomAuthHttpServerTest extends HttpAuthBase {
               Driver.Serialization.PROTOBUF, null, configuration);
 
       if (isBasicAuth) {
-        ConstraintSecurityHandler securityHandler =
+        HandlerWrapper securityHandler =
                 avaticaServer.configureBasicAuthentication(server, 
configuration);
         securityHandler.setHandler(avaticaHandler);
         avaticaHandler = securityHandler;
@@ -238,6 +238,14 @@ public class CustomAuthHttpServerTest extends HttpAuthBase 
{
       return null;
     }
 
+    @Override public String getKerberosServiceName() {
+      return null;
+    }
+
+    @Override public String getKerberosHostName() {
+      return null;
+    }
+
     @Override public String[] getAllowedRoles() {
       return new String[0];
     }
@@ -289,6 +297,14 @@ public class CustomAuthHttpServerTest extends HttpAuthBase 
{
       return null;
     }
 
+    @Override public String getKerberosServiceName() {
+      return null;
+    }
+
+    @Override public String getKerberosHostName() {
+      return null;
+    }
+
     @Override public String[] getAllowedRoles() {
       return new String[] { "users" };
     }
diff --git 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpQueryStringParameterRemoteUserExtractorTest.java
 
b/server/src/test/java/org/apache/calcite/avatica/server/HttpQueryStringParameterRemoteUserExtractorTest.java
index 33d91bc..b6d992b 100644
--- 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpQueryStringParameterRemoteUserExtractorTest.java
+++ 
b/server/src/test/java/org/apache/calcite/avatica/server/HttpQueryStringParameterRemoteUserExtractorTest.java
@@ -97,6 +97,14 @@ public class HttpQueryStringParameterRemoteUserExtractorTest 
extends HttpAuthBas
       return null;
     }
 
+    @Override public String getKerberosServiceName() {
+      return null;
+    }
+
+    @Override public String getKerberosHostName() {
+      return null;
+    }
+
     @Override public boolean supportsImpersonation() {
       // Impersonation is allowed
       return true;
diff --git 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerBuilderTest.java
 
b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerBuilderTest.java
deleted file mode 100644
index 41bb88b..0000000
--- 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerBuilderTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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/[email protected]", 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/[email protected]", 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/[email protected]", 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/[email protected]", 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/[email protected]", "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/[email protected]", "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/[email protected]", "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/[email protected]", "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
diff --git 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithJaasTest.java
 
b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithJaasTest.java
deleted file mode 100644
index 93794b9..0000000
--- 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithJaasTest.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * 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.ConnectionConfig;
-import org.apache.calcite.avatica.ConnectionConfigImpl;
-import org.apache.calcite.avatica.SpnegoTestUtil;
-import org.apache.calcite.avatica.remote.AvaticaCommonsHttpClientImpl;
-import org.apache.calcite.avatica.remote.CommonsHttpClientPoolCache;
-
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.apache.kerby.kerberos.kerb.KrbException;
-import org.apache.kerby.kerberos.kerb.client.KrbConfig;
-import org.apache.kerby.kerberos.kerb.client.KrbConfigKey;
-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.Assume;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.security.Principal;
-import java.security.PrivilegedExceptionAction;
-import java.util.Properties;
-import java.util.Set;
-import javax.security.auth.Subject;
-import javax.security.auth.kerberos.KerberosTicket;
-
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for SPNEGO with Kerberos. Purely testing SPNEGO, not the Avatica 
"protocol" on top
- * of that HTTP. This variant of the test requires that the user use JAAS 
configuration to
- * perform server-side login.
- */
-public class HttpServerSpnegoWithJaasTest {
-  private static final Logger LOG = 
LoggerFactory.getLogger(HttpServerSpnegoWithJaasTest.class);
-
-  private static SimpleKdcServer kdc;
-  private static HttpServer httpServer;
-
-  private static KrbConfig clientConfig;
-
-  private static int kdcPort;
-
-  private static File clientKeytab;
-  private static File serverKeytab;
-
-  private static File serverSpnegoConfigFile;
-
-  private static boolean isKdcStarted = false;
-  private static boolean isHttpServerStarted = false;
-
-  private static URL httpServerUrl;
-
-  @BeforeClass public static void setupKdc() throws Exception {
-    kdc = new SimpleKdcServer();
-    File target = SpnegoTestUtil.TARGET_DIR;
-    assertTrue(target.exists());
-
-    File kdcDir = new File(target, 
HttpServerSpnegoWithJaasTest.class.getSimpleName());
-    if (kdcDir.exists()) {
-      SpnegoTestUtil.deleteRecursively(kdcDir);
-    }
-    kdcDir.mkdirs();
-    kdc.setWorkDir(kdcDir);
-
-    kdc.setKdcHost(SpnegoTestUtil.KDC_HOST);
-    kdcPort = SpnegoTestUtil.getFreePort();
-    kdc.setAllowTcp(true);
-    kdc.setAllowUdp(false);
-    kdc.setKdcTcpPort(kdcPort);
-
-    LOG.info("Starting KDC server at {}:{}", SpnegoTestUtil.KDC_HOST, kdcPort);
-
-    kdc.init();
-    kdc.start();
-    isKdcStarted = true;
-
-    try (FileInputStream fis = new FileInputStream(new File(kdcDir, 
"krb5.conf"));
-        InputStreamReader isr = new InputStreamReader(fis, 
StandardCharsets.UTF_8);
-        BufferedReader r = new BufferedReader(isr)) {
-      String line;
-      while ((line = r.readLine()) != null) {
-        LOG.debug("KRB5 Config line: {}", line);
-      }
-    }
-
-    File keytabDir = new File(target, 
HttpServerSpnegoWithJaasTest.class.getSimpleName()
-        + "_keytabs");
-    if (keytabDir.exists()) {
-      SpnegoTestUtil.deleteRecursively(keytabDir);
-    }
-    keytabDir.mkdirs();
-    setupUsers(keytabDir);
-
-    clientConfig = new KrbConfig();
-    clientConfig.setString(KrbConfigKey.KDC_HOST, SpnegoTestUtil.KDC_HOST);
-    clientConfig.setInt(KrbConfigKey.KDC_TCP_PORT, kdcPort);
-    clientConfig.setString(KrbConfigKey.DEFAULT_REALM, SpnegoTestUtil.REALM);
-
-    serverSpnegoConfigFile = new File(kdcDir, "server-spnego.conf");
-    SpnegoTestUtil.writeSpnegoConf(serverSpnegoConfigFile, serverKeytab);
-
-    // Kerby sets "java.security.krb5.conf" for us!
-    System.setProperty("java.security.auth.login.config", 
serverSpnegoConfigFile.toString());
-    // http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/...
-    //    tutorials/BasicClientServer.html#useSub
-    System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
-    //System.setProperty("sun.security.spnego.debug", "true");
-    //System.setProperty("sun.security.krb5.debug", "true");
-
-    // Create and start an HTTP server configured only to allow SPNEGO requests
-    // We're not using `withAutomaticLogin(File)` which means we're relying on 
JAAS to log the
-    // server in.
-    httpServer = new HttpServer.Builder()
-        .withPort(0)
-        .withSpnego(SpnegoTestUtil.SERVER_PRINCIPAL, SpnegoTestUtil.REALM)
-        .withHandler(new SpnegoTestUtil.AuthenticationRequiredAvaticaHandler())
-        .build();
-    httpServer.start();
-    isHttpServerStarted = true;
-
-    httpServerUrl = new URL("http://"; + SpnegoTestUtil.KDC_HOST + ":" + 
httpServer.getPort());
-    LOG.info("HTTP server running at {}", httpServerUrl);
-
-    SpnegoTestUtil.refreshJaasConfiguration();
-  }
-
-  @AfterClass public static void stopKdc() throws Exception {
-    if (isHttpServerStarted) {
-      LOG.info("Stopping HTTP server at {}", httpServerUrl);
-      httpServer.stop();
-    }
-
-    if (isKdcStarted) {
-      LOG.info("Stopping KDC on {}", kdcPort);
-      kdc.stop();
-    }
-  }
-
-  private static void setupUsers(File keytabDir) throws KrbException {
-    String clientPrincipal = SpnegoTestUtil.CLIENT_PRINCIPAL.substring(0,
-        SpnegoTestUtil.CLIENT_PRINCIPAL.indexOf('@'));
-    clientKeytab = new File(keytabDir, clientPrincipal.replace('/', '_') + 
".keytab");
-    if (clientKeytab.exists()) {
-      SpnegoTestUtil.deleteRecursively(clientKeytab);
-    }
-    LOG.info("Creating {} with keytab {}", clientPrincipal, clientKeytab);
-    SpnegoTestUtil.setupUser(kdc, clientKeytab, clientPrincipal);
-
-    String serverPrincipal = SpnegoTestUtil.SERVER_PRINCIPAL.substring(0,
-        SpnegoTestUtil.SERVER_PRINCIPAL.indexOf('@'));
-    serverKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + 
".keytab");
-    if (serverKeytab.exists()) {
-      SpnegoTestUtil.deleteRecursively(serverKeytab);
-    }
-    LOG.info("Creating {} with keytab {}", SpnegoTestUtil.SERVER_PRINCIPAL, 
serverKeytab);
-    SpnegoTestUtil.setupUser(kdc, serverKeytab, 
SpnegoTestUtil.SERVER_PRINCIPAL);
-  }
-
-  @Test public void testNormalClientsDisallowed() throws Exception {
-    LOG.info("Connecting to {}", httpServerUrl.toString());
-    HttpURLConnection conn = (HttpURLConnection) 
httpServerUrl.openConnection();
-    conn.setRequestMethod("GET");
-    // Authentication should fail because we didn't provide anything
-    assertEquals(401, conn.getResponseCode());
-  }
-
-  @Test public void testAuthenticatedClientsAllowed() throws Exception {
-    Assume.assumeThat("Test disabled on Windows", File.separatorChar, is('/'));
-
-    // Create the subject for the client
-    final Subject clientSubject = AvaticaJaasKrbUtil.loginUsingKeytab(
-        SpnegoTestUtil.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);
-    LOG.info("Using TGT with etype: {}", tgt.getSessionKey().getAlgorithm());
-
-    // The name of the principal
-    final String principalName = clientPrincipals.iterator().next().getName();
-
-    // Run this code, logged in as the subject (the client)
-    byte[] response = Subject.doAs(clientSubject, new 
PrivilegedExceptionAction<byte[]>() {
-      @Override public byte[] run() throws Exception {
-        // Logs in with Kerberos via GSS
-        GSSManager gssManager = GSSManager.getInstance();
-        Oid oid = new Oid(SpnegoTestUtil.JGSS_KERBEROS_TICKET_OID);
-        GSSName gssClient = gssManager.createName(principalName, 
GSSName.NT_USER_NAME);
-        GSSCredential credential = gssManager.createCredential(gssClient,
-            GSSCredential.DEFAULT_LIFETIME, oid, GSSCredential.INITIATE_ONLY);
-
-        Properties props = new Properties();
-        ConnectionConfig config = new ConnectionConfigImpl(props);
-
-        PoolingHttpClientConnectionManager pool = 
CommonsHttpClientPoolCache.getPool(config);
-
-        // Passes the GSSCredential into the HTTP client implementation
-        final AvaticaCommonsHttpClientImpl httpClient =
-            new AvaticaCommonsHttpClientImpl(httpServerUrl);
-        httpClient.setGSSCredential(credential);
-        httpClient.setHttpClientPool(pool);
-
-        return httpClient.send(new byte[0]);
-      }
-    });
-
-    // We should get a response which is "OK" with our client's name
-    assertNotNull(response);
-    assertEquals("OK " + SpnegoTestUtil.CLIENT_PRINCIPAL,
-        new String(response, StandardCharsets.UTF_8));
-  }
-}
-
-// End HttpServerSpnegoWithJaasTest.java
diff --git 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
 
b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
index 43d1895..318af48 100644
--- 
a/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
+++ 
b/server/src/test/java/org/apache/calcite/avatica/server/HttpServerSpnegoWithoutJaasTest.java
@@ -129,8 +129,8 @@ public class HttpServerSpnegoWithoutJaasTest {
     // Kerby sets "java.security.krb5.conf" for us!
     System.clearProperty("java.security.auth.login.config");
     System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
-    //System.setProperty("sun.security.spnego.debug", "true");
-    //System.setProperty("sun.security.krb5.debug", "true");
+    System.setProperty("sun.security.spnego.debug", "true");
+    System.setProperty("sun.security.krb5.debug", "true");
 
     // Create and start an HTTP server configured only to allow SPNEGO requests
     // We use `withAutomaticLogin(File)` here which should invalidate the need 
to do JAAS config
@@ -234,7 +234,7 @@ public class HttpServerSpnegoWithoutJaasTest {
 
     // We should get a response which is "OK" with our client's name
     assertNotNull(response);
-    assertEquals("OK " + SpnegoTestUtil.CLIENT_PRINCIPAL,
+    assertEquals("OK " + SpnegoTestUtil.CLIENT_NAME,
         new String(response, StandardCharsets.UTF_8));
   }
 }
diff --git a/site/_docs/security.md b/site/_docs/security.md
index 70f644b..d7cf02c 100644
--- a/site/_docs/security.md
+++ b/site/_docs/security.md
@@ -9,6 +9,7 @@ auth_types:
   - { name: "Kerberos with SPNEGO", anchor: 
"kerberos-with-spnego-authentication" }
   - { name: "Custom Authentication", anchor: "custom-authentication" }
   - { name: "Client implementation", anchor: "client-implementation" }
+  - { name: "TLS", anchor: "tls" }
 ---
 <!--
 {% comment %}
@@ -164,6 +165,10 @@ HttpServer server = new HttpServer.Builder()
 
 #### JAAS Configuration File Login
 
+**Since Avatica 1.20.0, Jetty has removed this functionality which means that 
Avatica
+also does not support Avatica server login via JAAS configuration file. The 
Avatica
+programmatic login is the only manner to do this.**
+
 A JAAS configuration file can be set via the system property 
`java.security.auth.login.config`.
 The user must set this property when launching their Java application invoking 
the Avatica server.
 The presence of this file will automatically perform login as necessary in the 
first use
@@ -191,6 +196,20 @@ com.sun.security.jgss.accept  {
 
 Ensure the `keyTab` and `principal` attributes are set correctly for your 
system.
 
+#### Additional Allowed Realms
+
+Versions of Avatica prior to 1.20.0 provided API to specify a list of 
`additionalAllowedRealms`.
+While this API could have been leveraged by other integrators of Avatica, the 
only provided
+usage of this API was to specify additional Kerberos realms (realms other than 
the kerberos
+realm which the server's principal was a part of) which should be allowed to 
authenticate
+against the Avatica server.
+
+With the Jetty update in Avatica 1.20.0, this functionality was removed 
without replacement.
+Any user with valid Kerberos credentials which can be validated based on the 
krb5.conf file
+on the host where the Avatica server runs should be capable of authenticating 
against Avatica.
+Consult your JVM to determine where the default krb5.conf file is loaded from 
and the Java
+system property to use if you need to override this file.
+
 ### Impersonation
 
 Impersonation is a feature of the Avatica server which allows the Avatica 
clients
@@ -286,8 +305,21 @@ these implementations as it is likely correct.
 ### SPNEGO
 
 For information on building SPNEGO support by hand, consult 
[RFC-4559](https://tools.ietf.org/html/rfc4559)
-which describes how the authentication handshake, through use of the 
"WWW-authenticate=Negotiate"
-HTTP header, is used to authenticate a client.
+which describes how the authentication handshake, through use of the 
`WWW-Authenticate=Negotiate`
+HTTP header, is used to authenticate a client. Prior to Avatica 1.20.0, this 
handshake is done
+for every HTTP call to the Avatica server.
+
+Starting in Avatica 1.20.0, Avatica was updated to use a newer version of 
Jetty which includes
+the ability to perform one SPNEGO-based authentication handshake but then set 
a cookie which
+can be used to re-identify the client without performing subsequent SPNEGO 
handshakes.
+
+This is a notable change because it will effectively reduce the number of HTTP 
calls that an Avatica
+client has to make to the server which, for often results in a near 2x 
performance improvement (as there
+is a lower-bound of 1's of milliseconds per HTTP call). However, if the cookie 
is compromised, another
+client could potentially access Avatica as the user for whom the cookie was 
set for. Because of this, it
+is important to configure the Avatica server to [use TLS](#tls) to 
authenticate its clients.
+
+See more information in 
[CALCITE-4152](https://issues.apache.org/jira/browse/CALCITE-4152).
 
 ### Password-based
 
@@ -298,3 +330,17 @@ properties are used to identify the client with the 
server. If the underlying da
 these are set via the traditional "user" and "password" properties in the 
Avatica
 JDBC driver. This also implies that adding HTTP-level authentication in 
Avatica is likely
 superfluous.
+
+## TLS
+
+Deploying the Avatica server with TLS is common practice, like it is for any 
HTTP server. To do this,
+use the method `withTls(File, String, File, String)` to provide the server's 
TLS private key (a.k.a keystore)
+and the certificate authority's public key (a.k.a. truststore) as a Java Key 
Store (JKS) files, along with
+passwords to validate that the JKS files have not been tampered with.
+
+{% highlight java %}
+HttpServer server = new HttpServer.Builder()
+    .withTLS(new File("/avatica/server.jks"), "MyKeystorePassword",
+        new File("/avatica/truststore.jks"), "MyTruststorePassword")
+    .build();
+{% endhighlight %}
diff --git a/standalone-server/build.gradle.kts 
b/standalone-server/build.gradle.kts
index b3b0bde..fa9678e 100644
--- a/standalone-server/build.gradle.kts
+++ b/standalone-server/build.gradle.kts
@@ -84,7 +84,7 @@ tasks {
 
     shadowJar {
         manifest {
-            attributes["Main-Class"] = 
"org.apache.calcite.avatica.server.StandaloneServer"
+            attributes["Main-Class"] = 
"org.apache.calcite.avatica.standalone.StandaloneServer"
         }
         archiveClassifier.set("shadow")
         configurations = listOf(shaded)
@@ -97,9 +97,6 @@ tasks {
             "com.google.common",
             "com.google.protobuf",
             "javax.servlet",
-            "org.apache.log4j",
-            "org.eclipse.jetty",
-            "org.slf4j",
             "org.apache.http",
             "org.apache.commons"
         ).forEach {
diff --git 
a/standalone-server/src/main/java/org/apache/calcite/avatica/standalone/StandaloneServer.java
 
b/standalone-server/src/main/java/org/apache/calcite/avatica/standalone/StandaloneServer.java
index 1b94c55..686ffa6 100644
--- 
a/standalone-server/src/main/java/org/apache/calcite/avatica/standalone/StandaloneServer.java
+++ 
b/standalone-server/src/main/java/org/apache/calcite/avatica/standalone/StandaloneServer.java
@@ -29,6 +29,7 @@ import com.beust.jcommander.Parameter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.util.Locale;
 
 /**
@@ -53,6 +54,14 @@ public class StandaloneServer {
       description = "Print the help message")
   private boolean help = false;
 
+  @Parameter(names = { "--principal" }, required = false,
+      description = "Kerberos principal (optiona)")
+  private String kerberosPrincipal = null;
+
+  @Parameter(names = { "--keytab" }, required = false,
+      description = "Kerberos keytab (optional)", converter = 
ToFileConverter.class)
+  private File kerberosKeytab = null;
+
   private HttpServer server;
 
   public void start() {
@@ -67,10 +76,19 @@ public class StandaloneServer {
       LocalService service = new LocalService(meta);
 
       // Construct the server
-      this.server = new HttpServer.Builder()
+      HttpServer.Builder builder = new HttpServer.Builder()
           .withHandler(service, serialization)
-          .withPort(port)
-          .build();
+          .withPort(port);
+
+      if (kerberosPrincipal != null && kerberosKeytab != null) {
+        System.out.println("Configuring Avatica to use SPENGO");
+        builder.withSpnego(kerberosPrincipal)
+            .withAutomaticLogin(kerberosKeytab);
+      } else {
+        System.out.println("Not configuring Avatica authentication");
+      }
+
+      server = builder.build();
 
       // Then start it
       server.start();
@@ -136,6 +154,20 @@ public class StandaloneServer {
   }
 
   /**
+   * Converter from String to a File.
+   */
+  public static class ToFileConverter implements IStringConverter<File> {
+    @Override public File convert(String value) {
+      File f = new File(value);
+      if (!f.isFile()) {
+        String msg = "Kerberos keytab '" + value + "' appears to not be a 
file";
+        throw new IllegalArgumentException(msg);
+      }
+      return f;
+    }
+  }
+
+  /**
    * Codes for exit conditions
    */
   private enum ExitCodes {
diff --git a/standalone-server/src/main/resources/log4j.properties 
b/standalone-server/src/main/resources/log4j.properties
index c2a9652..c8f6c5b 100644
--- a/standalone-server/src/main/resources/log4j.properties
+++ b/standalone-server/src/main/resources/log4j.properties
@@ -19,8 +19,8 @@
 log4j.rootLogger=INFO, A1
 
 # A1 goes to the console
-log4j.appender.A1=org.apache.calcite.avatica.standalone.shaded.org.apache.log4j.ConsoleAppender
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
 
 # Set the pattern for each log message
-log4j.appender.A1.layout=org.apache.calcite.avatica.standalone.shaded.org.apache.log4j.PatternLayout
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
 log4j.appender.A1.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} - %m%n

Reply via email to