[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