[java-identity-provider] branch maint-4 updated: IDP-2039 - Add audit logging to login flows

Scott Cantor cantor.2 at osu.edu
Tue Dec 6 19:02:56 UTC 2022


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch maint-4
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=10c4e7753de72b4d6d3ac3a5ac2ce65c33606b65

The following commit(s) were added to refs/heads/maint-4 by this push:
     new 10c4e7753 IDP-2039 - Add audit logging to login flows
10c4e7753 is described below

commit 10c4e7753de72b4d6d3ac3a5ac2ce65c33606b65
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Dec 6 14:02:52 2022 -0500

    IDP-2039 - Add audit logging to login flows
    
    https://shibboleth.atlassian.net/browse/IDP-2039
    
    Adjust servlet to populate CertificateContext.
    Add extractors for cert issuer/subject.
    Audit the X509 flows.
---
 .../net/shibboleth/idp/authn/AuthnAuditFields.java | 14 ++++++
 .../impl/CertificateIssuerAuditExtractor.java      | 51 ++++++++++++++++++++++
 .../impl/CertificateSubjectAuditExtractor.java     | 51 ++++++++++++++++++++++
 .../authn/impl/ValidateExternalAuthentication.java |  2 +-
 .../shibboleth/idp/authn/impl/X509AuthServlet.java | 15 +++++++
 .../idp/flows/authn/x509-authn-beans.xml           | 35 ++++++++++++++-
 .../idp/flows/authn/x509-internal-authn-beans.xml  | 50 ++++++++++++++++++++-
 7 files changed, 215 insertions(+), 3 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java
index 4c2c3caf2..dca19db27 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java
@@ -59,6 +59,20 @@ public final class AuthnAuditFields {
      */
     @Nonnull @NotEmpty public static final String AUTHN_RESULT = "AR";
 
+    /**
+     * X.509 cerificate subject.
+     * 
+     * @since 4.3.0
+     */
+    @Nonnull @NotEmpty public static final String X509_SUBJECT = "X509S";
+
+    /**
+     * X.509 cerificate subject.
+     * 
+     * @since 4.3.0
+     */
+    @Nonnull @NotEmpty public static final String X509_ISSUER = "X509I";
+
     /** Constructor. */
     private AuthnAuditFields() {
 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateIssuerAuditExtractor.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateIssuerAuditExtractor.java
new file mode 100644
index 000000000..40f68925a
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateIssuerAuditExtractor.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.audit.impl;
+
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.CertificateContext;
+
+/** {@link Function} that returns the issuer of a client certificate. */
+public class CertificateIssuerAuditExtractor implements Function<ProfileRequestContext,String> {
+
+    /** {@inheritDoc} */
+    @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+
+        final AuthenticationContext authnCtx = input.getSubcontext(AuthenticationContext.class);
+        if (authnCtx != null) {
+            final CertificateContext cc = authnCtx.getSubcontext(CertificateContext.class);
+            if (cc != null) {
+                final Certificate cert = cc.getCertificate();
+                if (cert instanceof X509Certificate) {
+                    return ((X509Certificate) cert).getIssuerX500Principal().getName();
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateSubjectAuditExtractor.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateSubjectAuditExtractor.java
new file mode 100644
index 000000000..9bd7e1f67
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateSubjectAuditExtractor.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.audit.impl;
+
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.CertificateContext;
+
+/** {@link Function} that returns the subject of a client certificate. */
+public class CertificateSubjectAuditExtractor implements Function<ProfileRequestContext,String> {
+
+    /** {@inheritDoc} */
+    @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+
+        final AuthenticationContext authnCtx = input.getSubcontext(AuthenticationContext.class);
+        if (authnCtx != null) {
+            final CertificateContext cc = authnCtx.getSubcontext(CertificateContext.class);
+            if (cc != null) {
+                final Certificate cert = cc.getCertificate();
+                if (cert instanceof X509Certificate) {
+                    return ((X509Certificate) cert).getSubjectX500Principal().getName();
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java
index 8e44c598f..b0f0a3ddd 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java
@@ -67,7 +67,7 @@ import org.slf4j.LoggerFactory;
  * {@link AbstractValidationAction#handleError(ProfileRequestContext, AuthenticationContext, Exception, String)}
  * method is called.
  */
-public class ValidateExternalAuthentication extends AbstractValidationAction {
+public class ValidateExternalAuthentication extends AbstractAuditingValidationAction {
 
     /** Default prefix for metrics. */
     @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.external"; 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
index 515b16498..9efc7ff0c 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
@@ -34,9 +34,12 @@ import javax.servlet.http.HttpServletResponse;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.ExternalAuthentication;
 import net.shibboleth.idp.authn.ExternalAuthenticationException;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.CertificateContext;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 
+import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.security.SecurityException;
 import org.opensaml.security.trust.TrustEngine;
 import org.opensaml.security.x509.BasicX509Credential;
@@ -155,6 +158,18 @@ public class X509AuthServlet extends HttpServlet {
             log.debug("End-entity X.509 certificate found with subject '{}', issued by '{}'",
                     cert.getSubjectDN().getName(), cert.getIssuerDN().getName());
             
+            final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
+            final AuthenticationContext authnCtx = prc.getSubcontext(AuthenticationContext.class);
+            if (authnCtx != null) {
+                final CertificateContext cc = authnCtx.getSubcontext(CertificateContext.class, true);
+                cc.setCertificate(cert);
+                if (certs.length > 1) {
+                    for (int i = 1; i < certs.length; i++) {
+                        cc.getIntermediates().add(certs[i]);
+                    }
+                }
+            }
+
             if (trustEngine != null) {
                 try {
                     final BasicX509Credential cred = new BasicX509Credential(cert);
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml
index 587d43217..d8f926814 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml
@@ -29,10 +29,43 @@
         p:metricName="net.shibboleth.idp.authn.x509"
         p:addDefaultPrincipals="#{getObject('shibboleth.authn.X509.addDefaultPrincipals') ?: %{idp.authn.X509.addDefaultPrincipals:true}}"
         p:classifiedMessages="#{getObject('shibboleth.authn.X509.ClassifiedMessageMap')}"
-        p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}" />
+        p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}"
+        p:populateAuditContextAction="#{%{idp.authn.X509.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('shibboleth.authn.X509.PopulateAuditContext') : null}"
+        p:writeAuditLogAction="#{%{idp.authn.X509.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuditLog') : null}" />
 
     <bean id="PopulateSubjectCanonicalizationContext"
         class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
         p:availableFlows-ref="shibboleth.PostLoginSubjectCanonicalizationFlows" />
 
+    <!-- Audit logging beans. -->
+
+    <util:map id="shibboleth.authn.AuditFormattingMap">
+        <entry key="#{'%{idp.authn.X509.audit.category:Shibboleth-Audit.X509}'.trim()}"
+            value="#{'%{idp.authn.X509.audit.format:%a|%T|%SP|%s|%AF|%X509S|%X509I|%AR|%UA}'.trim()}" />
+    </util:map>
+
+    <bean id="shibboleth.authn.X509.DefaulAuditExtractors" parent="shibboleth.authn.DefaulAuditExtractors" lazy-init="true"
+            class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+             <map merge="true">
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_SUBJECT"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateSubjectAuditExtractor" />
+                </entry>
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_ISSUER"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateIssuerAuditExtractor" />
+                </entry>
+             </map>
+        </property>
+    </bean>
+    
+    <bean id="shibboleth.authn.X509.PopulateAuditContext" parent="shibboleth.authn.AbstractPopulateAuditContext" lazy-init="true"
+        p:fieldExtractors="#{getObject('shibboleth.authn.X509.AuditExtractors') ?: getObject('shibboleth.authn.X509.DefaulAuditExtractors')}"
+        p:clearAuditContext="true" />
+
 </beans>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml
index c2baae8e7..1e3d38f54 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml
@@ -18,6 +18,20 @@
     <bean class="net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor" />
     <bean class="net.shibboleth.idp.profile.impl.ProfileActionBeanPostProcessor" />
 
+    <!-- Default message map. -->
+    <util:map id="shibboleth.authn.X509Internal.ClassifiedMessageMap">
+        <entry key="RequestUnsupported">
+            <list>
+                <value>RequestUnsupported</value>
+            </list>
+        </entry>
+        <entry key="InvalidCredentials">
+            <list>
+                <value>InvalidCredentials</value>
+            </list>
+        </entry>
+    </util:map>
+
     <import resource="conditional:%{idp.home}/conf/authn/x509-internal-authn-config.xml" />
     
     <bean id="ExtractX509CertificateFromRequest"
@@ -27,7 +41,10 @@
     <bean id="ValidateX509Certificate"
             class="net.shibboleth.idp.authn.impl.ValidateCredentials" scope="prototype"
             p:addDefaultPrincipals="#{getObject('shibboleth.authn.X509.addDefaultPrincipals') ?: %{idp.authn.X509Internal.addDefaultPrincipals:true}}"
-            p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}">
+            p:classifiedMessages="#{getObject('shibboleth.authn.X509Internal.ClassifiedMessageMap')}"
+            p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}"
+            p:populateAuditContextAction="#{%{idp.authn.X509Internal.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('shibboleth.authn.X509Internal.PopulateAuditContext') : null}"
+            p:writeAuditLogAction="#{%{idp.authn.X509Internal.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuditLog') : null}">
         <property name="validators">
             <bean id="x509" class="net.shibboleth.idp.authn.impl.X509CertificateCredentialValidator"
                 p:trustEngine="#{getObject('shibboleth.authn.X509.TrustEngine')}"
@@ -39,4 +56,35 @@
         class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
         p:availableFlows-ref="shibboleth.PostLoginSubjectCanonicalizationFlows" />
 
+    <!-- Audit logging beans. -->
+
+    <util:map id="shibboleth.authn.AuditFormattingMap">
+        <entry key="#{'%{idp.authn.X509Internal.audit.category:Shibboleth-Audit.X509Internal}'.trim()}"
+            value="#{'%{idp.authn.X509Internal.audit.format:%a|%T|%SP|%s|%AF|%X509S|%X509I|%AR|%UA}'.trim()}" />
+    </util:map>
+
+    <bean id="shibboleth.authn.X509Internal.DefaulAuditExtractors" parent="shibboleth.authn.DefaulAuditExtractors" lazy-init="true"
+            class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+             <map merge="true">
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_SUBJECT"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateSubjectAuditExtractor" />
+                </entry>
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_ISSUER"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateIssuerAuditExtractor" />
+                </entry>
+             </map>
+        </property>
+    </bean>
+    
+    <bean id="shibboleth.authn.X509Internal.PopulateAuditContext" parent="shibboleth.authn.AbstractPopulateAuditContext" lazy-init="true"
+        p:fieldExtractors="#{getObject('shibboleth.authn.X509Internal.AuditExtractors') ?: getObject('shibboleth.authn.X509Internal.DefaulAuditExtractors')}"
+        p:clearAuditContext="true" />
+        
 </beans>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list