You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2018/11/17 17:20:38 UTC

[12/34] jena git commit: JENA-1627: Native https support

JENA-1627: Native https support

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

Branch: refs/heads/master
Commit: 2f9486416061986ed58004cb9d75dfd215830c72
Parents: 0e9c55a
Author: Andy Seaborne <an...@apache.org>
Authored: Fri Nov 9 20:46:15 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Nov 13 15:39:14 2018 +0000

----------------------------------------------------------------------
 .../org/apache/jena/fuseki/jetty/AuthMode.java  |  15 +-
 .../apache/jena/fuseki/jetty/JettyHttps.java    | 125 ++++++++++++
 .../org/apache/jena/fuseki/jetty/JettyLib.java  |  20 +-
 .../apache/jena/fuseki/server/FusekiVocab.java  |   6 +-
 .../apache/jena/fuseki/main/FusekiServer.java   | 192 ++++++++++++++++---
 .../jena/fuseki/main/cmds/FusekiMain.java       |  86 +++++++--
 .../jena/fuseki/main/cmds/FusekiMainCmd.java    |   7 +-
 .../jena/fuseki/main/cmds/ServerConfig.java     |  11 +-
 8 files changed, 405 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java
index 2c6e30e..47db5b2 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java
@@ -18,4 +18,17 @@
 
 package org.apache.jena.fuseki.jetty;
 
-public enum AuthMode { BASIC, DIGEST }
+/** Authorization scheme */
+public enum AuthMode { BASIC, DIGEST ;
+    public static AuthMode scheme(String name) {
+        if ( name == null )
+            return null;
+        name = name.toLowerCase();
+        switch(name) {
+            case "basic": return BASIC;
+            case "digest": return DIGEST;
+            default:
+                throw new IllegalArgumentException("no recognized as an authorization scheme: "+name);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
new file mode 100644
index 0000000..5ca3a91
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
@@ -0,0 +1,125 @@
+/*
+ * 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.jena.fuseki.jetty;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.*;
+import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/** Library of functions to help with setting Jetty up with HTTPS.
+ * This code is not supposed to be fully general.
+ * It sets up "http" to redirect to "https".
+ */
+public class JettyHttps {
+    
+    /*
+    * Useful documentation:
+    *   http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html
+    *   https://medium.com/vividcode/enable-https-support-with-self-signed-certificate-for-embedded-jetty-9-d3a86f83e9d9
+    *
+    * Generate a self-signed certificate
+    *   keytool -keystore mykey.jks -alias mykey -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -genkey -validity 3650
+    *
+    * Access with curl
+    *     curl -v -k https://localhost:8443/
+    *     curl -v -k -L http://localhost:8080/
+    */
+
+    /**
+     * Create a HTTPS Jetty server for the {@link ServletContextHandler}
+     * <p>  
+     * If httpPort is -1, don't add http otherwise make http redirect to https. 
+     */
+    public static Server jettyServerHttps(ServletContextHandler handler, String keystore, String certPassword, int httpPort, int httpsPort) {
+        // Server handling http and https.
+        Server jettyServer = server(keystore, certPassword, httpPort, httpsPort);
+        if ( httpPort > 0 ) {
+            // Redirect http to https.
+            // Order matters. Check https and bounce if http as first choice.
+            SecuredRedirectHandler srh = new SecuredRedirectHandler();
+            JettyLib.addHandler(jettyServer, srh);
+        }
+        JettyLib.addHandler(jettyServer, handler);
+        return jettyServer;
+    }
+    
+    /** Build the server - http and https connectors.
+     * If httpPort is -1, don't add http.
+     */ 
+    private static Server server(String keystore, String certPassword, int httpPort, int httpsPort) {
+        Server server = new Server();
+        if ( httpPort > 0 ) {
+            ServerConnector plainConnector = httpConnector(server, httpPort, httpsPort);
+            server.addConnector(plainConnector);
+        }
+        ServerConnector httpsConnector = httpsConnector(server, httpsPort, keystore, certPassword);
+        server.addConnector(httpsConnector);
+        return server;
+    }
+
+    /** Add HTTP to a {@link Server}, setting the secure redirection port. */
+    private static ServerConnector httpConnector(Server server, int httpPort, int httpsPort) {
+        HttpConfiguration http_config = httpConfiguration();
+        http_config.setSendServerVersion(false);
+        if ( httpPort >  0 ) {
+            http_config.setSecureScheme(HttpScheme.HTTPS.asString());
+            http_config.setSecurePort(httpsPort);
+        }
+        ServerConnector plainConnector = new ServerConnector(server, new HttpConnectionFactory(http_config));
+        plainConnector.setPort(httpPort);
+        return plainConnector;
+    }
+    
+    /** Add HTTPS to a {@link Server}. */
+    private static ServerConnector httpsConnector(Server server, int httpsPort, String keystore, String certPassword) {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystore);
+        sslContextFactory.setKeyStorePassword(certPassword);
+        
+        SecureRequestCustomizer src = new SecureRequestCustomizer();
+        src.setStsMaxAge(2000);
+        src.setStsIncludeSubDomains(true);
+
+        HttpConfiguration https_config = httpConfiguration();
+        https_config.setSecureScheme(HttpScheme.HTTPS.asString());
+        https_config.setSecurePort(httpsPort);
+        https_config.addCustomizer(src);
+
+        // HTTPS Connector
+        ServerConnector sslConnector = new ServerConnector(server,
+            new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
+            new HttpConnectionFactory(https_config));
+        sslConnector.setPort(httpsPort);
+        return sslConnector;
+    }
+
+    /** HTTP configuration with setting for Fuseki workload. No "secure" settings. */ 
+    private static HttpConfiguration httpConfiguration() {
+        HttpConfiguration http_config = new HttpConfiguration();
+        // Some people do try very large operations ... really, should use POST.
+        http_config.setRequestHeaderSize(512 * 1024);
+        http_config.setOutputBufferSize(1024 * 1024);
+//      http_config.setResponseHeaderSize(8192);
+        http_config.setSendServerVersion(false);
+        return http_config;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
index 616fbb7..30d061a 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
@@ -25,8 +25,6 @@ import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.security.*;
 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
 import org.eclipse.jetty.security.authentication.DigestAuthenticator;
-//import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-//import org.eclipse.jetty.security.authentication.DigestAuthenticator;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.HandlerList;
@@ -66,21 +64,28 @@ public class JettyLib {
      * for adding the {@code pathspec} to apply it to.
      */
      public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore) {
-         return makeSecurityHandler(realm, userStore, "**");
+         return makeSecurityHandler(realm, userStore, "**", dftAuthMode);
      }
 
-    /**
+     /** Create a Jetty {@link SecurityHandler} for basic authentication. 
+      * See {@linkplain #addPathConstraint(ConstraintSecurityHandler, String)}
+      * for adding the {@code pathspec} to apply it to.
+      */
+      public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore, AuthMode authMode) {
+          return makeSecurityHandler(realm, userStore, "**", authMode);
+      }
+
+      /**
      * Digest requires an extra round trip so it is unfriendly to API
      * or scripts that stream.
      */
-     // [AuthScheme] Default
-     public static AuthMode authMode = AuthMode.DIGEST; 
+     public static AuthMode dftAuthMode = AuthMode.DIGEST; 
 
       /** Create a Jetty {@link SecurityHandler} for basic authentication. 
      * See {@linkplain #addPathConstraint(ConstraintSecurityHandler, String)}
      * for adding the {@code pathspec} to apply it to.
      */
-     public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore, String role) {
+     public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore, String role, AuthMode authMode) {
         // role can be "**" for any authenticated user.
         Objects.requireNonNull(userStore);
         Objects.requireNonNull(role);
@@ -95,7 +100,6 @@ public class JettyLib {
         loginService.setUserStore(userStore);
         loginService.setIdentityService(identService);
         
-        // [AuthScheme]
         securityHandler.setLoginService(loginService);
         securityHandler.setAuthenticator( authMode == AuthMode.BASIC ? new BasicAuthenticator() : new DigestAuthenticator() );
         if ( realm != null )

http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
index f6ebca3..371d317 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
@@ -34,8 +34,12 @@ public class FusekiVocab
 
     public static final Property pServices          = property("services");
     public static final Property pServiceName       = property("name");
-    public static final Property pAllowedUsers      = property("allowedUsers");
     
+    public static final Property pAllowedUsers      = property("allowedUsers");
+    public static final Property pPasswordFile      = property("passwd");
+    public static final Property pRealm             = property("realm");
+    public static final Property pAuth              = property("auth");
+
     public static final Property pServiceQueryEP                = property("serviceQuery");
     public static final Property pServiceUpdateEP               = property("serviceUpdate");
     public static final Property pServiceUploadEP               = property("serviceUpload");

http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
index 23469b1..30819eb 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
@@ -20,6 +20,7 @@ package org.apache.jena.fuseki.main;
 
 import static java.util.Objects.requireNonNull;
 
+import java.io.IOException;
 import java.util.*;
 import java.util.function.Predicate;
 
@@ -36,12 +37,11 @@ import org.apache.jena.fuseki.build.FusekiConfig;
 import org.apache.jena.fuseki.build.RequestAuthorization;
 import org.apache.jena.fuseki.ctl.ActionPing;
 import org.apache.jena.fuseki.ctl.ActionStats;
+import org.apache.jena.fuseki.jetty.AuthMode;
 import org.apache.jena.fuseki.jetty.FusekiErrorHandler1;
+import org.apache.jena.fuseki.jetty.JettyHttps;
 import org.apache.jena.fuseki.jetty.JettyLib;
-import org.apache.jena.fuseki.server.DataAccessPoint;
-import org.apache.jena.fuseki.server.DataAccessPointRegistry;
-import org.apache.jena.fuseki.server.DataService;
-import org.apache.jena.fuseki.server.Operation;
+import org.apache.jena.fuseki.server.*;
 import org.apache.jena.fuseki.servlets.ActionService;
 import org.apache.jena.fuseki.servlets.AuthFilter;
 import org.apache.jena.fuseki.servlets.FusekiFilter;
@@ -51,8 +51,11 @@ import org.apache.jena.rdf.model.Model;
 import org.apache.jena.rdf.model.Resource;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.assembler.AssemblerUtils;
+import org.apache.jena.sparql.util.graph.GraphUtils;
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
@@ -115,22 +118,31 @@ public class FusekiServer {
     }
 
     public final Server server;
-    private int port;
+    private final int httpPort;
+    private final int httpsPort;
+    private final ServletContext servletContext;
+
+    private FusekiServer(int httpPort, Server server) {
+        this(httpPort, -1, server, 
+            ((ServletContextHandler)server.getHandler()).getServletContext()
+            );
+    }
 
-    private FusekiServer(int port, Server server) {
+    private FusekiServer(int httpPort, int httpsPort, Server server, ServletContext fusekiServletContext) {
         this.server = server;
-        // This should be the same.
-        //this.port = ((ServerConnector)server.getConnectors()[0]).getPort();
-        this.port = port;
+        this.httpPort = httpPort;
+        this.httpsPort = httpsPort;
+        this.servletContext = fusekiServletContext;
     }
 
     /**
-     * Return the port begin used.
+     * Return the port being used.
      * This will be the give port, which defaults to 3330, or
      * the one actually allocated if the port was 0 ("choose a free port").
+     * If https in use, this is the HTTPS port.
      */
     public int getPort() {
-        return port;
+        return httpsPort > 0 ? httpsPort : httpPort;
     }
 
     /** Get the underlying Jetty server which has also been set up. */
@@ -138,11 +150,11 @@ public class FusekiServer {
         return server;
     }
 
-    /** Get the {@link ServletContext}.
+    /** Get the {@link ServletContext} used for Fuseki processing.
      * Adding new servlets is possible with care.
      */
     public ServletContext getServletContext() {
-        return ((ServletContextHandler)server.getHandler()).getServletContext();
+        return servletContext;
     }
 
     /** Get the {@link DataAccessPointRegistry}.
@@ -164,16 +176,29 @@ public class FusekiServer {
      */
     public FusekiServer start() {
         try { server.start(); }
-        catch (Exception e) { throw new FusekiException(e); }
-        if ( port == 0 )
-            port = ((ServerConnector)server.getConnectors()[0]).getLocalPort();
-        Fuseki.serverLog.info("Start Fuseki (port="+port+")");
+        
+        catch (IOException ex) {
+            if ( ex.getCause() instanceof java.security.UnrecoverableKeyException )
+                // Unbundle for clearer message.
+                throw new FusekiException(ex.getMessage());
+            throw new FusekiException(ex);
+        }
+        catch (IllegalStateException ex) {
+            throw new FusekiException(ex.getMessage());
+        }
+        catch (Exception ex) {
+            throw new FusekiException(ex);
+        }
+        if ( httpsPort > 0 )
+            Fuseki.serverLog.info("Start Fuseki (port="+httpPort+"/"+httpsPort+")");
+        else
+            Fuseki.serverLog.info("Start Fuseki (port="+getPort()+")");
         return this;
     }
 
     /** Stop the server. */
     public void stop() {
-        Fuseki.serverLog.info("Stop Fuseki (port="+port+")");
+        Fuseki.serverLog.info("Stop Fuseki (port="+getPort()+")");
         try { server.stop(); }
         catch (Exception e) { throw new FusekiException(e); }
     }
@@ -190,11 +215,17 @@ public class FusekiServer {
         private final ServiceDispatchRegistry  serviceDispatch;
         // Default values.
         private int                      serverPort         = 3330;
+        private int                      serverHttpsPort    = -1;
         private boolean                  networkLoopback    = false;
         private boolean                  verbose            = false;
         private boolean                  withStats          = false;
         private boolean                  withPing           = false;
         private RequestAuthorization     serverAllowedUsers = null;
+        private String                   passwordFile       = null;
+        private String                   realm              = null;
+        private AuthMode                 authMode           = null;
+        private String                   httpsKeystore        = null;
+        private String                   httpsKeystorePasswd  = null;
         // Other servlets to add.
         private List<Pair<String, HttpServlet>> servlets    = new ArrayList<>();
         private List<Pair<String, Filter>> filters          = new ArrayList<>();
@@ -412,7 +443,7 @@ public class FusekiServer {
             Model model = AssemblerUtils.readAssemblerFile(filename);
 
             Resource server = FusekiConfig.findServer(model);
-            serverAllowedUsers = FusekiBuilder.allowedUsers(server);
+            processServerLevel(server);
             
             // Process server and services, whether via server ja:services or, if absent, by finding by type.
             // Side effect - sets global context.
@@ -422,6 +453,58 @@ public class FusekiServer {
         }
 
         /**
+         * Server level setting specific to Fuseki main.
+         * General settings done by {@link FusekiConfig#processServerConfiguration}.
+         */
+        private void processServerLevel(Resource server) {
+            if ( server == null )
+                return;
+            // Extract settings - the server building is done in buildSecurityHandler,
+            // buildAccessControl.  Dataset and graph level happen in assemblers. 
+            passwordFile = GraphUtils.getAsStringValue(server, FusekiVocab.pPasswordFile);
+            if ( passwordFile != null )
+                passwordFile(passwordFile);
+            realm = GraphUtils.getAsStringValue(server, FusekiVocab.pRealm);
+            if ( realm == null )
+                realm = "TripleStore"; 
+            realm(realm);
+            
+            String authStr = GraphUtils.getAsStringValue(server, FusekiVocab.pAuth);
+            authMode = AuthMode.scheme(authStr);
+            
+            serverAllowedUsers = FusekiBuilder.allowedUsers(server);
+        }
+
+        /**
+         * Set the realm used for HTTP digest authentication.
+         * The default is "TripleStore". 
+         */
+        public Builder realm(String realm) {
+            this.realm = realm;
+            return this;
+        }
+
+        /**
+         * Set the password file. This will be used to build a {@link #securityHandler
+         * security handler} if one is not supplied. Setting null clears any previous entry.
+         * The file should be 
+         * <a href="https://www.eclipse.org/jetty/documentation/current/configuring-security.html#hash-login-service">Eclipse jetty password file</a>.
+         */
+        public Builder passwordFile(String passwordFile) {
+            this.passwordFile = passwordFile;
+            return this;
+        }
+
+        public Builder https(int httpsPort, String certStore, String certStorePasswd) {
+            requireNonNull(certStore, "certStore");
+            requireNonNull(certStorePasswd, "certStorePasswd");
+            this.httpsKeystore = certStore;
+            this.httpsKeystorePasswd = certStorePasswd;
+            this.serverHttpsPort = httpsPort;
+            return this;
+        }
+        
+        /**
          * Add the given servlet with the {@code pathSpec}. These servlets are added so
          * that they are checked after the Fuseki filter for datasets and before the
          * static content handler (which is the last servlet) used for
@@ -510,29 +593,68 @@ public class FusekiServer {
          * Build a server according to the current description.
          */
         public FusekiServer build() {
+            if ( securityHandler == null && passwordFile != null )
+                securityHandler = buildSecurityHandler();
             ServletContextHandler handler = buildFusekiContext();
-            // Use HandlerCollection for several ServletContextHandlers and thus several ServletContext.
-            Server server = jettyServer(serverPort, networkLoopback);
-            server.setHandler(handler);
-            return new FusekiServer(serverPort, server);
+            
+            Server server;
+            if ( serverHttpsPort == -1 ) {
+                // HTTP
+                server = jettyServer(handler, serverPort);
+            } else {
+                // HTTPS, no http redirection.
+                server = jettyServerHttps(handler, serverPort, serverHttpsPort, httpsKeystore, httpsKeystorePasswd);
+            }
+            if ( networkLoopback ) 
+                applyLocalhost(server);
+            return new FusekiServer(serverPort, serverHttpsPort, server, handler.getServletContext());
         }
 
-        /** Build one configured Fuseki in one unit - same ServletContext, same dispatch ContextPath */  
+        /** Build one configured Fuseki processor (ServletContext), same dispatch ContextPath */  
         private ServletContextHandler buildFusekiContext() {
             ServletContextHandler handler = buildServletContext(contextPath);
             ServletContext cxt = handler.getServletContext();
             Fuseki.setVerbose(cxt, verbose);
             servletAttr.forEach((n,v)->cxt.setAttribute(n, v));
-            // Clone to isolate from any future changes.
+            
+            // Clone to isolate from any future changes (reusing the builder).
             ServiceDispatchRegistry.set(cxt, new ServiceDispatchRegistry(serviceDispatch));
             DataAccessPointRegistry.set(cxt, new DataAccessPointRegistry(dataAccessPoints));
             JettyLib.setMimeTypes(handler);
             servletsAndFilters(handler);
+            buildAccessControl(cxt);
+            
             // Start services.
             DataAccessPointRegistry.get(cxt).forEach((name, dap)->dap.getDataService().goActive());
             return handler;
         }
         
+        private ConstraintSecurityHandler buildSecurityHandler() {
+            if ( passwordFile == null )
+                return null;
+            UserStore userStore = JettyLib.makeUserStore(passwordFile);
+            return JettyLib.makeSecurityHandler(realm, userStore, authMode);
+        }
+        
+        private void buildAccessControl(ServletContext cxt) {
+            // -- Access control
+            if ( securityHandler != null && securityHandler instanceof ConstraintSecurityHandler ) {
+                ConstraintSecurityHandler csh = (ConstraintSecurityHandler)securityHandler;
+               if ( serverAllowedUsers != null )
+                   JettyLib.addPathConstraint(csh, "/*");
+               else {
+                    DataAccessPointRegistry.get(cxt).forEach((name, dap)-> {
+                        DatasetGraph dsg = dap.getDataService().getDataset();
+                        if ( dap.getDataService().allowedUsers() != null ) {
+                            // Not need if whole server ACL'ed.
+                            JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name));
+                            JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name)+"/*");
+                        }
+                    });
+               }
+            }
+        }
+        
         /** Build a ServletContextHandler */
         private ServletContextHandler buildServletContext(String contextPath) {
             if ( contextPath == null || contextPath.isEmpty() )
@@ -595,7 +717,7 @@ public class FusekiServer {
         }
 
         /** Jetty server with one connector/port. */
-        private static Server jettyServer(int port, boolean loopback) {
+        private static Server jettyServer(ServletContextHandler handler, int port) {
             Server server = new Server();
             HttpConnectionFactory f1 = new HttpConnectionFactory();
             // Some people do try very large operations ... really, should use POST.
@@ -607,9 +729,23 @@ public class FusekiServer {
             ServerConnector connector = new ServerConnector(server, f1);
             connector.setPort(port);
             server.addConnector(connector);
-            if ( loopback )
-                connector.setHost("localhost");
+            server.setHandler(handler);
             return server;
         }
+        
+        /** Jetty server with https. */
+        private static Server jettyServerHttps(ServletContextHandler handler, int httpPort, int httpsPort, String keystore, String certPassword) {
+            return JettyHttps.jettyServerHttps(handler, keystore, certPassword, httpPort, httpsPort);
+        }
+
+        /** Restrict connectors to localhost */
+        private static void applyLocalhost(Server server) {
+            Connector[] connectors = server.getConnectors();
+            for ( int i = 0 ; i < connectors.length ; i++ ) {
+                if ( connectors[i] instanceof ServerConnector ) {
+                    ((ServerConnector)connectors[i]).setHost("localhost");
+                }
+            }
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
index 09edf8b..8fc596e 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
@@ -33,6 +33,8 @@ import arq.cmdline.ModDatasetAssembler;
 import jena.cmd.ArgDecl;
 import jena.cmd.CmdException;
 import org.apache.jena.assembler.exceptions.AssemblerException;
+import org.apache.jena.atlas.json.JSON;
+import org.apache.jena.atlas.json.JsonObject;
 import org.apache.jena.atlas.lib.DateTimeUtils;
 import org.apache.jena.atlas.lib.FileOps;
 import org.apache.jena.atlas.logging.FmtLog;
@@ -55,14 +57,14 @@ import org.apache.jena.riot.RDFLanguages;
 import org.apache.jena.sparql.core.DatasetGraphFactory;
 import org.apache.jena.sys.JenaSystem;
 import org.apache.jena.system.Txn;
-import org.apache.jena.tdb.TDB;
 import org.apache.jena.tdb.TDBFactory;
 import org.apache.jena.tdb.transaction.TransactionManager;
 import org.apache.jena.tdb2.DatabaseMgr;
 import org.slf4j.Logger;
 
 public class FusekiMain extends CmdARQ {
-        private static int defaultPort = 3030;
+        private static int defaultPort          = 3030;
+        private static int defaultHttpsPort     = 3043;
         
         private static ArgDecl  argMem          = new ArgDecl(ArgDecl.NoValue,  "mem");
         private static ArgDecl  argUpdate       = new ArgDecl(ArgDecl.NoValue,  "update", "allowUpdate");
@@ -74,13 +76,21 @@ public class FusekiMain extends CmdARQ {
         
         // No SPARQL dataset or services
         private static ArgDecl  argEmpty        = new ArgDecl(ArgDecl.NoValue,  "empty", "no-dataset");
+        private static ArgDecl  argGeneralQuerySvc = new ArgDecl(ArgDecl.HasValue, "general");
+        
         private static ArgDecl  argPort         = new ArgDecl(ArgDecl.HasValue, "port");
         private static ArgDecl  argLocalhost    = new ArgDecl(ArgDecl.NoValue,  "localhost", "local");
         private static ArgDecl  argTimeout      = new ArgDecl(ArgDecl.HasValue, "timeout");
         private static ArgDecl  argConfig       = new ArgDecl(ArgDecl.HasValue, "config", "conf");
         private static ArgDecl  argGZip         = new ArgDecl(ArgDecl.HasValue, "gzip");
         private static ArgDecl  argBase         = new ArgDecl(ArgDecl.HasValue, "base", "files");
-        private static ArgDecl  argGeneralQuerySvc = new ArgDecl(ArgDecl.HasValue, "general");
+        
+        private static ArgDecl  argHttps        = new ArgDecl(ArgDecl.HasValue, "https");
+        private static ArgDecl  argHttpsPort    = new ArgDecl(ArgDecl.HasValue, "httpsPort", "httpsport", "sport");
+        
+        private static ArgDecl  argPasswdFile   = new ArgDecl(ArgDecl.HasValue, "passwd");
+        private static ArgDecl  argRealm        = new ArgDecl(ArgDecl.HasValue, "realm");
+        
         
         // Same as --empty --validators --general=/sparql, --files=ARG
         
@@ -100,7 +110,7 @@ public class FusekiMain extends CmdARQ {
             return inner.buildServer();
         }
 
-        static void innerMain(String... argv) {
+        static void run(String... argv) {
             JenaSystem.init();
             new FusekiMain(argv).mainRun();
         }
@@ -144,8 +154,14 @@ public class FusekiMain extends CmdARQ {
             add(argSparqler, "--sparqler=DIR",
                 "Run with SPARQLer services Directory for static content");
             add(argValidators, "--validators", "Install validators");
+            
+            add(argHttps, "--https=CONF", "https certificate access details. JSON file { \"cert\":FILE , \"passwd\"; SECRET } ");
+            add(argHttpsPort, "--httpsPort=NUM", "https port (default port is 3043)");
+
+            // Disable - put in the configuration file.
+//            add(argPasswdFile, "--passwd=FILE", "Password file");
+//            add(argRealm, "--realm=REALM", "Realm name");
 
-            super.modVersion.addClass(TDB.class);
             super.modVersion.addClass(Fuseki.class);
         }
 
@@ -208,15 +224,9 @@ public class FusekiMain extends CmdARQ {
             // ---- Port
             serverConfig.port = defaultPort;
             
-            if ( contains(argPort) ) {
-                String portStr = getValue(argPort);
-                try {
-                    int port = Integer.parseInt(portStr);
-                    serverConfig.port = port;
-                } catch (NumberFormatException ex) {
-                    throw new CmdException(argPort.getKeyName() + " : bad port number: " + portStr);
-                }
-            }
+            if ( contains(argPort) )
+                serverConfig.port = portNumber(argPort);
+
             if ( contains(argLocalhost) )
                 serverConfig.loopback = true;
 
@@ -337,6 +347,30 @@ public class FusekiMain extends CmdARQ {
                 serverConfig.contentDirectory = filebase;
             }
 
+            if ( contains(argPasswdFile) )
+                serverConfig.passwdFile = getValue(argPasswdFile);
+            if ( contains(argRealm) )
+                serverConfig.realm =  getValue(argRealm);
+            
+            if ( contains(argHttpsPort) && ! contains(argHttps) )
+                throw new CmdException("https port given but not certificate dtails via --"+argHttps.getKeyName());
+            
+            if ( contains(argHttps) ) {
+                serverConfig.httpsPort = defaultHttpsPort;
+                if (  contains(argHttpsPort) )
+                    serverConfig.httpsPort = portNumber(argHttpsPort);
+                String httpsSetup = getValue(argHttps);
+                // The details go in a separate file that can be secured. 
+                JsonObject httpsConf = JSON.read(httpsSetup);
+                Path path = Paths.get(httpsSetup).toAbsolutePath();
+                String keystore = httpsConf.get("keystore").getAsString().value();
+                // Resolve relative to the https setup file.  
+                serverConfig.httpsKeystore = path.getParent().resolve(keystore).toString();
+                
+                serverConfig.httpsKeystorePasswd = httpsConf.get("passwd").getAsString().value();
+              
+            }
+            
 //            if ( contains(argGZip) ) {
 //                if ( !hasValueOfTrue(argGZip) && !hasValueOfFalse(argGZip) )
 //                    throw new CmdException(argGZip.getNames().get(0) + ": Not understood: " + getValue(argGZip));
@@ -344,11 +378,22 @@ public class FusekiMain extends CmdARQ {
 //            }
         }
         
+        private int portNumber(ArgDecl arg) {
+            String portStr = getValue(arg);
+            try {
+                int port = Integer.parseInt(portStr);
+                return port;
+            } catch (NumberFormatException ex) {
+                throw new CmdException(argPort.getKeyName() + " : bad port number: " + portStr);
+            }
+
+        }
+        
         @Override
         protected void exec() {
             try {
                 FusekiServer server = buildServer(serverConfig);
-                info(server, serverConfig);
+                info(server);
                 try {
                     server.start();
                 } catch (FusekiException ex) {
@@ -413,10 +458,19 @@ public class FusekiMain extends CmdARQ {
             if ( serverConfig.contentDirectory != null )
                 builder.staticFileBase(serverConfig.contentDirectory) ;
 
+            if ( serverConfig.passwdFile != null )
+                builder.passwordFile(serverConfig.passwdFile);
+
+            if ( serverConfig.realm != null )
+                builder.realm(serverConfig.realm);
+            
+            if ( serverConfig.httpsKeystore != null )
+                builder.https(serverConfig.httpsPort, serverConfig.httpsKeystore, serverConfig.httpsKeystorePasswd);
+           
             return builder.build();
         }
 
-        private void info(FusekiServer server, ServerConfig serverConfig) {
+        private void info(FusekiServer server) {
             if ( super.isQuiet() )
                 return;
 

http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
index a7ae54b..c19cf6b 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
@@ -29,7 +29,10 @@ import org.apache.jena.fuseki.system.FusekiLogging;
 
 public class FusekiMainCmd {
     // This class wraps FusekiBasicMain so that it can take control of logging setup.
-    // It does not depend via inheritance on any Jena code - FusekiBasicMain does.
+    // This class does not depend via inheritance on any Jena code
+    // and does not trigger Jena initialization. 
+    // FusekiLogging runs before any Jena code can trigger logging setup.
+    //
     // Inheritance causes initialization in the super class first, before class
     // initialization code in this class.
     
@@ -41,6 +44,6 @@ public class FusekiMainCmd {
      * syntax but not start it.
      */
     static public void main(String... argv) {
-        FusekiMain.innerMain(argv);
+        FusekiMain.run(argv);
     }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
index 2376a50..89f991c 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
@@ -25,7 +25,7 @@ import org.apache.jena.sparql.core.DatasetGraph;
  *  This is processed by {@link FusekiMain#buildServer}.
  */
 class ServerConfig {
-    /** Server port */
+    /** Server port. This is the http port when both http and https are active. */
     public int port;
     /** Loopback */
     public boolean loopback           = false;
@@ -48,4 +48,13 @@ class ServerConfig {
     /** An informative label */
     public String datasetDescription;
     public String contentDirectory    = null;
+    
+    // Server authentication
+    public String passwdFile          = null;
+    public String realm               = null;
+    
+    // https
+    public int httpsPort              = -1;
+    public String httpsKeystore       = null;
+    public String httpsKeystorePasswd = null;
 }
\ No newline at end of file