[Pki-devel] [PATCH] [WIP] Add external authentication support

Fraser Tweedale ftweedal at redhat.com
Mon Jan 11 12:05:28 UTC 2016


Hi all,

GSS-API authentication support is in progress.  The approach is
detailed in the design proposal[1], which is not complete.

The attached patch is provided for early review of the approach and
implementation.  It should not break existing behaviour for existing
authentication methods, but is not yet fully usable for externally
authenticated principals.

Some brief implementation notes:

- `ExternalAuthToken' class wraps an externally authenticated
  principal in order to provide reasonable values for common
  AuthToken attributes.  Many attributes are not yet implemented,
  and some never will be (i.e. some call sites may need to weaken
  their assumptions).

- There are ~9 explicit casts of principal from abstract `Principal'
  to `PKIPrincpial'; these sites need to be checked and probably
  updated in most cases, because (principal instanceof PKIPrincipal)
  is no longer a valid assumption.  Some are definitely broken.

- `AuthMethodInterceptor' currently treats all external
  authentication methods the same, allowing allowing access.  If
  needed, different external authn methods can be distinguished,
  allowing different access rules for different external authn
  methods.

- This patch does not configure the second tomcat AJP `Connector'
  required.  Also, the `Connector' needs to be "locked down" to a
  only allow traffic from the Apache frontend.  I need to confirm
  how to do this and clearly document it.

Regarding the design document, there is a lot more to come re:
authorization, especially for user-created objects such as secrets
in KRA.
-------------- next part --------------
From df1a7676c443e67328add77fea6b764ef5d211fa Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Tue, 22 Dec 2015 18:49:08 +1100
Subject: [PATCH] Add external authentication support

---
 .classpath                                         |   2 +
 base/ca/tomcat8/conf/Catalina/localhost/ca.xml     |   2 +
 .../certsrv/authentication/ExternalAuthToken.java  | 139 +++++++++++++++++++++
 .../org/dogtagpki/server/rest/ACLInterceptor.java  |  16 +--
 .../org/dogtagpki/server/rest/AccountService.java  |   5 +-
 .../server/rest/AuthMethodInterceptor.java         |  29 +++--
 .../server/rest/SessionContextInterceptor.java     |  30 ++---
 base/server/tomcat/src/CMakeLists.txt              |   8 ++
 .../cms/tomcat/ExternalAuthenticationValve.java    |  75 +++++++++++
 9 files changed, 264 insertions(+), 42 deletions(-)
 create mode 100644 base/common/src/com/netscape/certsrv/authentication/ExternalAuthToken.java
 create mode 100644 base/server/tomcat/src/com/netscape/cms/tomcat/ExternalAuthenticationValve.java

diff --git a/.classpath b/.classpath
index 9fd5144bf32f3a4af6b6992f7b5027ef55d9f2df..2cac0dedce4376283783067312981998fbfad4a9 100644
--- a/.classpath
+++ b/.classpath
@@ -54,7 +54,9 @@
 	<classpathentry kind="lib" path="/usr/share/java/apache-commons-lang.jar"/>
 	<classpathentry kind="lib" path="/usr/share/java/resteasy/resteasy-atom-provider.jar"/>
 	<classpathentry kind="lib" path="/usr/share/java/tomcat/catalina.jar"/>
+	<classpathentry kind="lib" path="/usr/share/java/tomcat/tomcat-coyote.jar"/>
 	<classpathentry kind="lib" path="/usr/share/java/tomcat/tomcat-util.jar"/>
+	<classpathentry kind="lib" path="/usr/share/java/tomcat/tomcat-util-scan.jar"/>
 	<classpathentry kind="lib" path="/usr/share/java/commons-io.jar"/>
 	<classpathentry kind="lib" path="/usr/lib/java/nuxwdog.jar"/>
 	<classpathentry kind="lib" path="/usr/lib/java/jss4.jar"/>
diff --git a/base/ca/tomcat8/conf/Catalina/localhost/ca.xml b/base/ca/tomcat8/conf/Catalina/localhost/ca.xml
index 46f270817a58282b950b75a15bb3bd052f178f0c..0268bc17e055b98198a9a44275319e77217c87fd 100644
--- a/base/ca/tomcat8/conf/Catalina/localhost/ca.xml
+++ b/base/ca/tomcat8/conf/Catalina/localhost/ca.xml
@@ -27,6 +27,8 @@
     <Manager
         secureRandomProvider="Mozilla-JSS" secureRandomAlgorithm="pkcs11prng"/>
 
+    <Valve className="com.netscape.cms.tomcat.ExternalAuthenticationValve" />
+
     <Valve className="com.netscape.cms.tomcat.SSLAuthenticatorWithFallback"
         alwaysUseSession="true"
         secureRandomProvider="Mozilla-JSS"
diff --git a/base/common/src/com/netscape/certsrv/authentication/ExternalAuthToken.java b/base/common/src/com/netscape/certsrv/authentication/ExternalAuthToken.java
new file mode 100644
index 0000000000000000000000000000000000000000..fff10e6a08dacbf8d6bb50bf65dff0eb2042e488
--- /dev/null
+++ b/base/common/src/com/netscape/certsrv/authentication/ExternalAuthToken.java
@@ -0,0 +1,139 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2015 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.certsrv.authentication;
+
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Enumeration;
+
+import org.apache.catalina.realm.GenericPrincipal;
+
+import netscape.security.x509.CertificateExtensions;
+import netscape.security.x509.X509CertImpl;
+
+import com.netscape.certsrv.usrgrp.Certificates;
+
+
+/**
+ * Authentication token that wraps an externally authenticated
+ * principal to return.
+ */
+public class ExternalAuthToken implements IAuthToken {
+
+    protected GenericPrincipal principal;
+
+    public ExternalAuthToken(GenericPrincipal principal) {
+        this.principal = principal;
+    }
+
+    public Enumeration<String> getElements() {
+        return null;
+    }
+
+    public Object get(String k) {
+        return null;
+    }
+
+    public boolean set(String k, String v) {
+        return false;
+    }
+
+    public String getInString(String k) {
+        if (k != null
+                && (k.equals(IAuthToken.USER_ID) || k.equals(IAuthToken.UID)))
+            return principal.getName();
+        else
+            return null;
+    }
+
+    public boolean set(String k, byte[] v) {
+        return false;
+    }
+
+    public byte[] getInByteArray(String k) {
+        return null;
+    }
+
+    public boolean set(String k, Integer v) {
+        return false;
+    }
+
+    public Integer getInInteger(String k) {
+        return null;
+    }
+
+    public boolean set(String k, BigInteger[] v) {
+        return false;
+    }
+
+    public BigInteger[] getInBigIntegerArray(String k) {
+        return null;
+    }
+
+    public boolean set(String k, Date v) {
+        return false;
+    }
+
+    public Date getInDate(String k) {
+        return null;
+    }
+
+    public boolean set(String k, String[] v) {
+        return false;
+    }
+
+    public String[] getInStringArray(String k) {
+        if (k != null && k.equals(IAuthToken.GROUPS))
+            return principal.getRoles();
+        else
+            return null;
+    }
+
+    public boolean set(String k, X509CertImpl v) {
+        return false;
+    }
+
+    public X509CertImpl getInCert(String k) {
+        return null;
+    }
+
+    public boolean set(String k, CertificateExtensions v) {
+        return false;
+    }
+
+    public CertificateExtensions getInCertExts(String k) {
+        return null;
+    }
+
+    public boolean set(String k, Certificates v) {
+        return false;
+    }
+
+    public Certificates getInCertificates(String k) {
+        return null;
+    }
+
+    public boolean set(String k, byte[][] v) {
+        return false;
+    }
+
+    public byte[][] getInByteArrayArray(String k) {
+        return null;
+    }
+}
diff --git a/base/server/cms/src/org/dogtagpki/server/rest/ACLInterceptor.java b/base/server/cms/src/org/dogtagpki/server/rest/ACLInterceptor.java
index 49001168130831bbb002711120891195b5d54ba5..3a76a5c793f4c8ba346c3979cee86fe7a75a7d7a 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/ACLInterceptor.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/ACLInterceptor.java
@@ -31,11 +31,13 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.catalina.realm.GenericPrincipal;
 import org.jboss.resteasy.core.ResourceMethodInvoker;
 import org.jboss.resteasy.spi.Failure;
 
 import com.netscape.certsrv.acls.ACLMapping;
 import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.authentication.ExternalAuthToken;
 import com.netscape.certsrv.authentication.IAuthToken;
 import com.netscape.certsrv.authorization.AuthzToken;
 import com.netscape.certsrv.authorization.EAuthzAccessDenied;
@@ -140,18 +142,12 @@ public class ACLInterceptor implements ContainerRequestFilter {
         if (principal != null)
             CMS.debug("ACLInterceptor: principal: " + principal.getName());
 
-        // If unrecognized principal, reject request.
-        if (principal != null && !(principal instanceof PKIPrincipal)) {
-            CMS.debug("ACLInterceptor: Invalid user principal.");
-            // audit comment: no Principal, no one to blame here
-            throw new ForbiddenException("Invalid user principal.");
-        }
-
-        PKIPrincipal pkiPrincipal = null;
         IAuthToken authToken = null;
         if (principal != null) {
-            pkiPrincipal = (PKIPrincipal) principal;
-            authToken = pkiPrincipal.getAuthToken();
+            if (principal instanceof PKIPrincipal)
+                authToken = ((PKIPrincipal) principal).getAuthToken();
+            else if (principal instanceof GenericPrincipal)
+                authToken = new ExternalAuthToken((GenericPrincipal) principal);
         }
 
         // If missing auth token, reject request.
diff --git a/base/server/cms/src/org/dogtagpki/server/rest/AccountService.java b/base/server/cms/src/org/dogtagpki/server/rest/AccountService.java
index 4e8e6e6f89fc065e2ad0c5fa616165de929142db..827e99e076585d0732bfde8ae795d6ae63648d5f 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/AccountService.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/AccountService.java
@@ -29,6 +29,7 @@ import javax.ws.rs.core.Request;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
+import org.apache.catalina.realm.GenericPrincipal;
 import org.apache.commons.lang.StringUtils;
 
 import com.netscape.certsrv.account.AccountInfo;
@@ -75,8 +76,10 @@ public class AccountService extends PKIService implements AccountResource {
 
             String email = user.getEmail();
             if (!StringUtils.isEmpty(email)) response.setEmail(email);
+        }
 
-            String[] roles = pkiPrincipal.getRoles();
+        if (principal instanceof GenericPrincipal) {
+            String[] roles = ((GenericPrincipal) principal).getRoles();
             response.setRoles(Arrays.asList(roles));
         }
 
diff --git a/base/server/cms/src/org/dogtagpki/server/rest/AuthMethodInterceptor.java b/base/server/cms/src/org/dogtagpki/server/rest/AuthMethodInterceptor.java
index ac0b2518cdc42528b7c0e94153f2b02777c26785..2a81813fad282ee943ea9eaae43291eb1397c686 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/AuthMethodInterceptor.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/AuthMethodInterceptor.java
@@ -139,22 +139,20 @@ public class AuthMethodInterceptor implements ContainerRequestFilter {
                 throw new ForbiddenException("Anonymous access not allowed.");
             }
 
-            // If unrecognized principal, reject request.
-            if (!(principal instanceof PKIPrincipal)) {
-                CMS.debug("AuthMethodInterceptor: unknown principal");
-                throw new ForbiddenException("Unknown user principal");
-            }
+            String authManager = "external";
+            if (principal instanceof PKIPrincipal) {
+                PKIPrincipal pkiPrincipal = (PKIPrincipal) principal;
+                IAuthToken authToken = pkiPrincipal.getAuthToken();
 
-            PKIPrincipal pkiPrincipal = (PKIPrincipal) principal;
-            IAuthToken authToken = pkiPrincipal.getAuthToken();
+                // If missing auth token, reject request.
+                if (authToken == null) {
+                    CMS.debug("AuthMethodInterceptor: missing authentication token");
+                    throw new ForbiddenException("Missing authentication token.");
+                }
 
-            // If missing auth token, reject request.
-            if (authToken == null) {
-                CMS.debug("AuthMethodInterceptor: missing authentication token");
-                throw new ForbiddenException("Missing authentication token.");
+                authManager = (String) authToken.get(AuthToken.TOKEN_AUTHMGR_INST_NAME);
             }
 
-            String authManager = (String) authToken.get(AuthToken.TOKEN_AUTHMGR_INST_NAME);
             CMS.debug("AuthMethodInterceptor: authentication manager: " + authManager);
 
             if (authManager == null) {
@@ -162,7 +160,12 @@ public class AuthMethodInterceptor implements ContainerRequestFilter {
                 throw new ForbiddenException("Missing authentication manager.");
             }
 
-            if (authMethods.isEmpty() || authMethods.contains(authManager) || authMethods.contains("*")) {
+            if (
+                authMethods.isEmpty()
+                || authManager.equals("external")
+                || authMethods.contains(authManager)
+                || authMethods.contains("*")
+            ) {
                 CMS.debug("AuthMethodInterceptor: access granted");
                 return;
             }
diff --git a/base/server/cms/src/org/dogtagpki/server/rest/SessionContextInterceptor.java b/base/server/cms/src/org/dogtagpki/server/rest/SessionContextInterceptor.java
index b6461abfdee36ea4eeba4d07da815482b02712ba..e8def14d9d77f046ef1dd52dd5ceacf0041ed543 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/SessionContextInterceptor.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/SessionContextInterceptor.java
@@ -29,9 +29,11 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.catalina.realm.GenericPrincipal;
 import org.jboss.resteasy.core.ResourceMethodInvoker;
 
 import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.authentication.ExternalAuthToken;
 import com.netscape.certsrv.authentication.IAuthToken;
 import com.netscape.certsrv.base.ForbiddenException;
 import com.netscape.certsrv.base.SessionContext;
@@ -80,21 +82,6 @@ public class SessionContextInterceptor implements ContainerRequestFilter {
 
         CMS.debug("SessionContextInterceptor: principal: " + principal.getName());
 
-        // If unrecognized principal, reject request.
-        if (!(principal instanceof PKIPrincipal)) {
-            CMS.debug("SessionContextInterceptor: Invalid user principal.");
-            throw new ForbiddenException("Invalid user principal.");
-        }
-
-        PKIPrincipal pkiPrincipal = (PKIPrincipal) principal;
-        IAuthToken authToken = pkiPrincipal.getAuthToken();
-
-        // If missing auth token, reject request.
-        if (authToken == null) {
-            CMS.debug("SessionContextInterceptor: No authorization token present.");
-            throw new ForbiddenException("No authorization token present.");
-        }
-
         SessionContext context = SessionContext.getContext();
 
         String ip = servletRequest.getRemoteAddr();
@@ -103,8 +90,15 @@ public class SessionContextInterceptor implements ContainerRequestFilter {
         Locale locale = getLocale(servletRequest);
         context.put(SessionContext.LOCALE, locale);
 
-        context.put(SessionContext.AUTH_TOKEN, authToken);
-        context.put(SessionContext.USER_ID, pkiPrincipal.getName());
-        context.put(SessionContext.USER, pkiPrincipal.getUser());
+        context.put(SessionContext.USER_ID, principal.getName());
+
+        if (principal instanceof PKIPrincipal) {
+            PKIPrincipal pkiPrincipal = (PKIPrincipal) principal;
+            context.put(SessionContext.AUTH_TOKEN, pkiPrincipal.getAuthToken());
+            context.put(SessionContext.USER, pkiPrincipal.getUser());
+        } else if (principal instanceof GenericPrincipal) {
+            context.put(SessionContext.AUTH_TOKEN,
+                    new ExternalAuthToken((GenericPrincipal) principal));
+        }
     }
 }
diff --git a/base/server/tomcat/src/CMakeLists.txt b/base/server/tomcat/src/CMakeLists.txt
index 669cc8883043062119afdb5b55db28828d09e92f..2e3658f4b12cdbc4515b0532c3c20b5c0194e84e 100644
--- a/base/server/tomcat/src/CMakeLists.txt
+++ b/base/server/tomcat/src/CMakeLists.txt
@@ -133,6 +133,13 @@ find_file(NUXWDOG_JAR
         /usr/share/java
 )
 
+find_file(TOMCAT_COYOTE_JAR
+    NAMES
+        tomcat-coyote.jar
+    PATHS
+        /usr/share/java/tomcat
+)
+
 # build pki-tomcat
 javac(pki-tomcat-classes
     SOURCES
@@ -140,6 +147,7 @@ javac(pki-tomcat-classes
     CLASSPATH
         ${SERVLET_JAR} ${TOMCAT_CATALINA_JAR} ${TOMCAT_UTIL_SCAN_JAR}
 		${NUXWDOG_JAR} ${APACHE_COMMONS_LANG_JAR} ${TOMCATJSS_JAR}
+		${TOMCAT_COYOTE_JAR}
     OUTPUT_DIR
         ${CMAKE_BINARY_DIR}/../../tomcat
 )
diff --git a/base/server/tomcat/src/com/netscape/cms/tomcat/ExternalAuthenticationValve.java b/base/server/tomcat/src/com/netscape/cms/tomcat/ExternalAuthenticationValve.java
new file mode 100644
index 0000000000000000000000000000000000000000..92cec5d301803be63dfb305b12aaca7985f6fc83
--- /dev/null
+++ b/base/server/tomcat/src/com/netscape/cms/tomcat/ExternalAuthenticationValve.java
@@ -0,0 +1,75 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2015 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.tomcat;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.valves.ValveBase;
+
+public class ExternalAuthenticationValve extends ValveBase {
+
+    public void invoke(Request req, Response resp)
+            throws IOException, ServletException {
+        System.out.println("ExternalAuthenticationValve; authType: "
+                + req.getAuthType());
+        System.out.println("ExternalAuthenticationValve; principal: "
+                + req.getUserPrincipal());
+        System.out.println(req.getCoyoteRequest().getAttributes().toString());
+
+        org.apache.coyote.Request coyoteReq = req.getCoyoteRequest();
+        Principal oldPrincipal = req.getUserPrincipal();
+
+        if (oldPrincipal != null) {
+            Integer numGroups = 0;
+            String numGroupsStr = (String)
+                coyoteReq.getAttribute("REMOTE_USER_GROUP_N");
+            if (numGroupsStr != null) {
+                try {
+                    numGroups = new Integer(numGroupsStr);
+                } catch (NumberFormatException e) {
+                    System.out.println("ExternalAuthenticationValve: invalid REMOTE_USER_GROUP_N value: " + e);
+                }
+            }
+
+            ArrayList<String> groups = new ArrayList<>();
+            for (int i = 1; i <= numGroups; i++) {
+                String k = "REMOTE_USER_GROUP_" + i;
+                String s = (String) coyoteReq.getAttribute(k);
+                if (s != null && !s.isEmpty())
+                    groups.add(s);
+                else
+                    System.out.println("ExternalAuthenticationValve: missing or empty attribute: " + k);
+            }
+
+            GenericPrincipal newPrincipal =
+                new GenericPrincipal(oldPrincipal.getName(), null, groups);
+
+            System.out.println("ExternalAuthenticationValve: setting new principal: " + newPrincipal);
+            req.setUserPrincipal(newPrincipal);
+        }
+
+        getNext().invoke(req, resp);
+    }
+}
-- 
2.5.0



More information about the Pki-devel mailing list