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

Fraser Tweedale ftweedal at redhat.com
Mon Jan 11 23:07:04 UTC 2016


On Mon, Jan 11, 2016 at 10:05:28PM +1000, Fraser Tweedale wrote:
> Hi all,
> 
> GSS-API authentication support is in progress.  The approach is
> detailed in the design proposal[1], which is not complete.
> 
Now with link!

[1] https://pagure.io/test_dogtag_designs/pull-request/8

> 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.

> 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