>From 07391274b0d2ca6c4b9f163f017abb35b6029f6b Mon Sep 17 00:00:00 2001
From: Christina Fu <cfu@redhat.com>
Date: Thu, 7 May 2015 12:14:19 -0700
Subject: [PATCH] Ticket 1160 audit logging needed: REST API auth/authz; kra
 for getKeyInfo    - REST API auth/authz

---
 .../cms/src/com/netscape/cms/realm/PKIRealm.java   |  99 +++++++++++-
 .../org/dogtagpki/server/rest/ACLInterceptor.java  | 177 ++++++++++++++++++---
 base/server/cmsbundle/src/LogMessages.properties   |   2 +
 .../authentication/CertUserDBAuthentication.java   |   3 +-
 4 files changed, 256 insertions(+), 25 deletions(-)

diff --git a/base/server/cms/src/com/netscape/cms/realm/PKIRealm.java b/base/server/cms/src/com/netscape/cms/realm/PKIRealm.java
index bd64de148cfd1fc5db759aa23e685eea3a4963a8..8c07445fbaf2f815b8a92566ad0de22e2d83eccf 100644
--- a/base/server/cms/src/com/netscape/cms/realm/PKIRealm.java
+++ b/base/server/cms/src/com/netscape/cms/realm/PKIRealm.java
@@ -16,6 +16,8 @@ import com.netscape.certsrv.authentication.IAuthSubsystem;
 import com.netscape.certsrv.authentication.IAuthToken;
 import com.netscape.certsrv.authentication.ICertUserDBAuthentication;
 import com.netscape.certsrv.authentication.IPasswdUserDBAuthentication;
+import com.netscape.certsrv.base.SessionContext;
+import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.usrgrp.EUsrGrpException;
 import com.netscape.certsrv.usrgrp.IGroup;
 import com.netscape.certsrv.usrgrp.IUGSubsystem;
@@ -31,6 +33,11 @@ import com.netscape.cms.servlet.common.AuthCredentials;
  */
 
 public class PKIRealm extends RealmBase {
+    protected ILogger mSignedAuditLogger = CMS.getSignedAuditLogger();
+    private final static String LOGGING_SIGNED_AUDIT_AUTH_FAIL =
+            "LOGGING_SIGNED_AUDIT_AUTH_FAIL_4";
+    private final static String LOGGING_SIGNED_AUDIT_AUTH_SUCCESS =
+            "LOGGING_SIGNED_AUDIT_AUTH_SUCCESS_3";
 
     @Override
     protected String getName() {
@@ -40,20 +47,43 @@ public class PKIRealm extends RealmBase {
     @Override
     public Principal authenticate(String username, String password) {
         logDebug("Authenticating username "+username+" with password.");
+        String auditMessage = null;
+        String auditSubjectID = ILogger.UNIDENTIFIED;
+        String attemptedAuditUID = username;
 
         try {
             IAuthSubsystem authSub = (IAuthSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTH);
             IAuthManager authMgr = authSub.getAuthManager(IAuthSubsystem.PASSWDUSERDB_AUTHMGR_ID);
+            SessionContext ctx = SessionContext.getContext();
+            ctx.put(SessionContext.AUTH_MANAGER_ID,IAuthSubsystem.PASSWDUSERDB_AUTHMGR_ID);
 
             AuthCredentials creds = new AuthCredentials();
             creds.set(IPasswdUserDBAuthentication.CRED_UID, username);
             creds.set(IPasswdUserDBAuthentication.CRED_PWD, password);
 
             IAuthToken authToken = authMgr.authenticate(creds); // throws exception if authentication fails
+            auditSubjectID = authToken.getInString(IAuthToken.USER_ID);
 
+
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTH_SUCCESS,
+                        auditSubjectID,
+                        ILogger.SUCCESS,
+                        IAuthSubsystem.PASSWDUSERDB_AUTHMGR_ID);
+
+            audit(auditMessage);
             return getPrincipal(username, authToken);
 
         } catch (Throwable e) {
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTH_FAIL,
+                        auditSubjectID,
+                        ILogger.FAILURE,
+                        IAuthSubsystem.PASSWDUSERDB_AUTHMGR_ID,
+                        attemptedAuditUID);
+            audit(auditMessage);
             e.printStackTrace();
         }
 
@@ -63,6 +93,13 @@ public class PKIRealm extends RealmBase {
     @Override
     public Principal authenticate(final X509Certificate certs[]) {
         logDebug("Authenticating certificate chain:");
+        if (certs.length == 0) {
+            logDebug("missing client cert");
+        }
+        String auditMessage = null;
+        // get the cert from the ssl client auth
+        String auditSubjectID = getAuditUserfromCert(certs[0]);
+        String attemptedAuditUID = ILogger.UNIDENTIFIED;
 
         try {
             X509CertImpl certImpls[] = new X509CertImpl[certs.length];
@@ -73,27 +110,60 @@ public class PKIRealm extends RealmBase {
                 // Convert sun.security.x509.X509CertImpl to netscape.security.x509.X509CertImpl
                 certImpls[i] = new X509CertImpl(cert.getEncoded());
             }
-
             IAuthSubsystem authSub = (IAuthSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTH);
             IAuthManager authMgr = authSub.getAuthManager(IAuthSubsystem.CERTUSERDB_AUTHMGR_ID);
+            SessionContext ctx = SessionContext.getContext();
+            ctx.put(SessionContext.AUTH_MANAGER_ID,IAuthSubsystem.CERTUSERDB_AUTHMGR_ID);
 
             AuthCredentials creds = new AuthCredentials();
             creds.set(ICertUserDBAuthentication.CRED_CERT, certImpls);
 
             IAuthToken authToken = authMgr.authenticate(creds); // throws exception if authentication fails
-
             String username = authToken.getInString(ICertUserDBAuthentication.TOKEN_USERID);
+            // reset it to the one authenticated with authManager
+            auditSubjectID = authToken.getInString(IAuthToken.USER_ID);
+
             logDebug("User ID: "+username);
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTH_SUCCESS,
+                        auditSubjectID,
+                        ILogger.SUCCESS,
+                        IAuthSubsystem.CERTUSERDB_AUTHMGR_ID);
 
+            audit(auditMessage);
             return getPrincipal(username, authToken);
 
         } catch (Throwable e) {
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTH_FAIL,
+                        auditSubjectID,
+                        ILogger.FAILURE,
+                        IAuthSubsystem.CERTUSERDB_AUTHMGR_ID,
+                        attemptedAuditUID);
+            audit(auditMessage);
             e.printStackTrace();
         }
 
         return null;
     }
 
+    private String getAuditUserfromCert(X509Certificate clientCert) {
+        String auditUID = null;
+        String certUID = clientCert.getSubjectDN().getName();
+        CMS.debug("PKIRealm.getAuditUserfromCert: certUID=" + certUID);
+
+        if (certUID != null) {
+            certUID = certUID.trim();
+
+            if (!(certUID.equals(""))) {
+                auditUID = certUID;
+            }
+        }
+        return auditUID;
+    }
+
     @Override
     protected Principal getPrincipal(String username) {
         return getPrincipal(username, (IAuthToken)null);
@@ -152,9 +222,34 @@ public class PKIRealm extends RealmBase {
      */
     public void logErr(String msg) {
         System.err.println(msg);
+        CMS.debug("PKIRealm.logErr: " + msg);
     }
 
     public void logDebug(String msg) {
         System.out.println("PKIRealm: "+msg);
+        CMS.debug("PKIRealm.logDebug: " + msg);
+    }
+
+    /**
+     * Signed Audit Log
+     *
+     * This method is called to store messages to the signed audit log.
+     * <P>
+     *
+     * @param msg signed audit log message
+     */
+    protected void audit(String msg) {
+        // in this case, do NOT strip preceding/trailing whitespace
+        // from passed-in String parameters
+
+        if (mSignedAuditLogger == null) {
+            return;
+        }
+
+        mSignedAuditLogger.log(ILogger.EV_SIGNED_AUDIT,
+                null,
+                ILogger.S_SIGNED_AUDIT,
+                ILogger.LL_SECURITY,
+                msg);
     }
 }
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 7ea5d74aa797b28d78a5e6ebaec6d96d6cd066ea..30e5cc7e1ff5aa6b549657425b865030ad69a350 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/ACLInterceptor.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/ACLInterceptor.java
@@ -42,6 +42,7 @@ import com.netscape.certsrv.authorization.EAuthzAccessDenied;
 import com.netscape.certsrv.authorization.IAuthzSubsystem;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.ForbiddenException;
+import com.netscape.certsrv.logging.ILogger;
 import com.netscape.cms.realm.PKIPrincipal;
 
 /**
@@ -49,6 +50,17 @@ import com.netscape.cms.realm.PKIPrincipal;
  */
 @Provider
 public class ACLInterceptor implements ContainerRequestFilter {
+    protected ILogger mSignedAuditLogger = CMS.getSignedAuditLogger();
+    private final static String LOGGING_SIGNED_AUDIT_AUTHZ_FAIL =
+            "LOGGING_SIGNED_AUDIT_AUTHZ_FAIL_5";
+    private final static String LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS =
+            "LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS_5";
+
+    private final static String LOGGING_ACL_PARSING_ERROR = "internal error: ACL parsing error";
+    private final static String LOGGING_NO_ACL_ACCESS_ALLOWED = "no ACL configured; OK";
+    private final static String LOGGING_MISSING_AUTH_TOKEN = "auth token not found";
+    private final static String LOGGING_MISSING_ACL_MAPPING = "ACL mapping not found; OK";
+    private final static String LOGGING_INVALID_ACL_MAPPING = "internal error: invalid ACL mapping";
 
     Properties properties;
 
@@ -93,30 +105,18 @@ public class ACLInterceptor implements ContainerRequestFilter {
                 .getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
         Method method = methodInvoker.getMethod();
         Class<?> clazz = methodInvoker.getResourceClass();
+        String auditInfo =  clazz.getSimpleName() + "." + method.getName();
 
-        CMS.debug("ACLInterceptor: " + clazz.getSimpleName() + "." + method.getName() + "()");
-
-        ACLMapping aclMapping = method.getAnnotation(ACLMapping.class);
-
-        // If not available, get ACL mapping for the class.
-        if (aclMapping == null) {
-            aclMapping = clazz.getAnnotation(ACLMapping.class);
-        }
-
-        // If still not available, it's unprotected, allow request.
-        if (aclMapping == null) {
-            CMS.debug("ACLInterceptor: No ACL mapping.");
-            return;
-        }
-
-        String name = aclMapping.value();
-        CMS.debug("ACLInterceptor: mapping: " + name);
+        CMS.debug("ACLInterceptor: " + auditInfo + "()");
+        String auditMessage = null;
+        String auditSubjectID = ILogger.UNIDENTIFIED;
 
         Principal principal = securityContext.getUserPrincipal();
 
         // If unauthenticated, reject request.
         if (principal == null) {
             CMS.debug("ACLInterceptor: No user principal provided.");
+            // audit comment: no Principal, no one to blame here
             throw new ForbiddenException("No user principal provided.");
         }
 
@@ -125,6 +125,7 @@ public class ACLInterceptor implements ContainerRequestFilter {
         // If unrecognized principal, reject request.
         if (!(principal instanceof PKIPrincipal)) {
             CMS.debug("ACLInterceptor: Invalid user principal.");
+            // audit comment: no Principal, no one to blame here
             throw new ForbiddenException("Invalid user principal.");
         }
 
@@ -133,10 +134,46 @@ public class ACLInterceptor implements ContainerRequestFilter {
 
         // If missing auth token, reject request.
         if (authToken == null) {
-            CMS.debug("ACLInterceptor: No authorization token present.");
+            CMS.debug("ACLInterceptor: No authentication token present.");
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTHZ_FAIL,
+                        auditSubjectID,
+                        ILogger.FAILURE,
+                        null, // resource
+                        null, // operation
+                        LOGGING_MISSING_AUTH_TOKEN + ":" + auditInfo);
+            audit(auditMessage);
             throw new ForbiddenException("No authorization token present.");
         }
+        auditSubjectID = authToken.getInString(IAuthToken.USER_ID);
 
+        ACLMapping aclMapping = method.getAnnotation(ACLMapping.class);
+
+        // If not available, get ACL mapping for the class.
+        if (aclMapping == null) {
+            aclMapping = clazz.getAnnotation(ACLMapping.class);
+        }
+
+        // If still not available, it's unprotected, allow request.
+        if (aclMapping == null) {
+            CMS.debug("ACLInterceptor: No ACL mapping.");
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS,
+                        auditSubjectID,
+                        ILogger.SUCCESS,
+                        null, //resource
+                        null, //operation
+                        LOGGING_MISSING_ACL_MAPPING + ":" + auditInfo); //info
+            audit(auditMessage);
+            return;
+        }
+
+        String name = aclMapping.value();
+        CMS.debug("ACLInterceptor: mapping: " + name);
+
+        String values[] = null;
         try {
             loadProperties();
 
@@ -145,19 +182,53 @@ public class ACLInterceptor implements ContainerRequestFilter {
             // If no property defined, allow request.
             if (value == null) {
                 CMS.debug("ACLInterceptor: No ACL configuration.");
+                // store a message in the signed audit log file
+                auditMessage = CMS.getLogMessage(
+                            LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS,
+                            auditSubjectID,
+                            ILogger.SUCCESS,
+                            null, //resource
+                            null, //operation
+                            LOGGING_NO_ACL_ACCESS_ALLOWED + ":" +auditInfo);
                 return;
             }
 
-            String values[] = value.split(",");
+            values = value.split(",");
 
             // If invalid mapping, reject request.
             if (values.length != 2) {
                 CMS.debug("ACLInterceptor: Invalid ACL mapping.");
+                // store a message in the signed audit log file
+                auditMessage = CMS.getLogMessage(
+                            LOGGING_SIGNED_AUDIT_AUTHZ_FAIL,
+                            auditSubjectID,
+                            ILogger.FAILURE,
+                            null, //resource
+                            null, //operation
+                            LOGGING_INVALID_ACL_MAPPING + ":" + auditInfo);
+
+                audit(auditMessage);
                 throw new ForbiddenException("Invalid ACL mapping.");
             }
 
             CMS.debug("ACLInterceptor: ACL: " + value);
 
+        } catch (IOException e) {
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTHZ_FAIL,
+                        auditSubjectID,
+                        ILogger.FAILURE,
+                        null, //resource
+                        null, //operation
+                        LOGGING_ACL_PARSING_ERROR + ":" + auditInfo);
+
+            audit(auditMessage);
+            e.printStackTrace();
+            throw new Failure(e);
+        }
+
+        try {
             // Check authorization.
             IAuthzSubsystem mAuthz = (IAuthzSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTHZ);
             AuthzToken authzToken = mAuthz.authorize(
@@ -168,22 +239,84 @@ public class ACLInterceptor implements ContainerRequestFilter {
 
             // If not authorized, reject request.
             if (authzToken == null) {
-                CMS.debug("ACLInterceptor: No authorization token present.");
+                String info = "No authorization token present.";
+                CMS.debug("ACLInterceptor: " + info);
+                // store a message in the signed audit log file
+                auditMessage = CMS.getLogMessage(
+                            LOGGING_SIGNED_AUDIT_AUTHZ_FAIL,
+                            auditSubjectID,
+                            ILogger.FAILURE,
+                            values[0], // resource
+                            values[1], // operation
+                            info);
+                audit(auditMessage);
                 throw new ForbiddenException("No authorization token present.");
             }
 
             CMS.debug("ACLInterceptor: access granted");
 
         } catch (EAuthzAccessDenied e) {
-            CMS.debug("ACLInterceptor: " + e.getMessage());
+            String info = e.getMessage();
+            CMS.debug("ACLInterceptor: " + info);
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTHZ_FAIL,
+                        auditSubjectID,
+                        ILogger.FAILURE,
+                        values[0], // resource
+                        values[1], // operation
+                        info);
+            audit(auditMessage);
             throw new ForbiddenException(e.toString());
 
-        } catch (IOException | EBaseException e) {
+        } catch (EBaseException e) {
+            String info = e.getMessage();
+            // store a message in the signed audit log file
+            auditMessage = CMS.getLogMessage(
+                        LOGGING_SIGNED_AUDIT_AUTHZ_FAIL,
+                        auditSubjectID,
+                        ILogger.FAILURE,
+                        values[0], // resource
+                        values[1], // operation
+                        info);
+            audit(auditMessage);
             e.printStackTrace();
             throw new Failure(e);
         }
 
         // Allow request.
+        // store a message in the signed audit log file
+        auditMessage = CMS.getLogMessage(
+                    LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS,
+                    auditSubjectID,
+                    ILogger.SUCCESS,
+                    values[0], // resource
+                    values[1], // operation
+                    auditInfo);
+        audit(auditMessage);
         return;
     }
+
+    /**
+     * Signed Audit Log
+     *
+     * This method is called to store messages to the signed audit log.
+     * <P>
+     *
+     * @param msg signed audit log message
+     */
+    protected void audit(String msg) {
+        // in this case, do NOT strip preceding/trailing whitespace
+        // from passed-in String parameters
+
+        if (mSignedAuditLogger == null) {
+            return;
+        }
+
+        mSignedAuditLogger.log(ILogger.EV_SIGNED_AUDIT,
+                null,
+                ILogger.S_SIGNED_AUDIT,
+                ILogger.LL_SECURITY,
+                msg);
+    }
 }
diff --git a/base/server/cmsbundle/src/LogMessages.properties b/base/server/cmsbundle/src/LogMessages.properties
index 10d9ae5ca58d4b60e6f5e782ff59045d3dc8e68f..6fbd43404c3e1ebea570651d3e864746defcddc0 100644
--- a/base/server/cmsbundle/src/LogMessages.properties
+++ b/base/server/cmsbundle/src/LogMessages.properties
@@ -2131,6 +2131,7 @@ LOGGING_SIGNED_AUDIT_CERT_STATUS_CHANGE_REQUEST_PROCESSED_7=<type=CERT_STATUS_CH
 #    e.g. "read" for an ACL statement containing "(read,write)"
 #
 LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS_4=<type=AUTHZ_SUCCESS>:[AuditEvent=AUTHZ_SUCCESS][SubjectID={0}][Outcome={1}][aclResource={2}][Op={3}] authorization success
+LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS_5=<type=AUTHZ_SUCCESS>:[AuditEvent=AUTHZ_SUCCESS][SubjectID={0}][Outcome={1}][aclResource={2}][Op={3}][Info={4}] authorization success
 #
 # LOGGING_SIGNED_AUDIT_AUTHZ_FAIL
 # - used when authorization has failed
@@ -2140,6 +2141,7 @@ LOGGING_SIGNED_AUDIT_AUTHZ_SUCCESS_4=<type=AUTHZ_SUCCESS>:[AuditEvent=AUTHZ_SUCC
 #    e.g. "read" for an ACL statement containing "(read,write)"
 #
 LOGGING_SIGNED_AUDIT_AUTHZ_FAIL_4=<type=AUTHZ_FAIL>:[AuditEvent=AUTHZ_FAIL][SubjectID={0}][Outcome={1}][aclResource={2}][Op={3}] authorization failure
+LOGGING_SIGNED_AUDIT_AUTHZ_FAIL_5=<type=AUTHZ_FAIL>:[AuditEvent=AUTHZ_FAIL][SubjectID={0}][Outcome={1}][aclResource={2}][Op={3}][Info={4}] authorization failure
 #
 # LOGGING_SIGNED_AUDIT_INTER_BOUNDARY_SUCCESS
 # - used when inter-CIMC_Boundary data transfer is successful
diff --git a/base/server/cmscore/src/com/netscape/cmscore/authentication/CertUserDBAuthentication.java b/base/server/cmscore/src/com/netscape/cmscore/authentication/CertUserDBAuthentication.java
index 573b736d4b5c97293cf61865cf00d25d9c88c1bb..998d7e2612e08ed7564421e7e3496a0b0d97aa8e 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/authentication/CertUserDBAuthentication.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/authentication/CertUserDBAuthentication.java
@@ -168,6 +168,7 @@ public class CertUserDBAuthentication implements IAuthManager, ICertUserDBAuthen
         try {
             user = (User) mCULocator.locateUser(certs);
         } catch (EUsrGrpException e) {
+            CMS.debug("CertUserDBAuthentication: cannot map certificate to any user");
             log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_AUTH_AGENT_AUTH_FAILED", x509Certs[0].getSerialNumber()
                     .toString(16), x509Certs[0].getSubjectDN().toString(), e.toString()));
             throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL"));
@@ -179,7 +180,7 @@ public class CertUserDBAuthentication implements IAuthManager, ICertUserDBAuthen
         // any unexpected error occurs like internal db down,
         // UGSubsystem only returns null for user.
         if (user == null) {
-            CMS.debug("Authentication: cannot map certificate to user");
+            CMS.debug("CertUserDBAuthentication: cannot map certificate to any user");
             log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_AUTH_AGENT_USER_NOT_FOUND"));
             throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL"));
         }
-- 
1.8.4.2

