From 7077e6ada1a88373d4c607b925bdaf3d45741bb3 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 7 Dec 2016 15:24:07 +1000 Subject: [PATCH 174/175] Add ExternalProcessConstraint for request validation Add the ExternalProcessConstraint profile policy constraint class. It can be configured to execute an arbitrary program that performs additional request validation, rejecting the request if it terminates with a nonzero exit status. Information about the request is conveyed in the subprocess' environment. Part of: https://pagure.io/dogtagpki/issue/1359 --- base/ca/shared/conf/registry.cfg | 5 +- .../constraint/ExternalProcessConstraint.java | 158 +++++++++++++++++++++ .../04-AddExternalProcessConstraintToRegistry | 67 +++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 base/server/cms/src/com/netscape/cms/profile/constraint/ExternalProcessConstraint.java create mode 100755 base/server/upgrade/10.4.0/04-AddExternalProcessConstraintToRegistry diff --git a/base/ca/shared/conf/registry.cfg b/base/ca/shared/conf/registry.cfg index 280c71388e8f1575a8785c2009b3c728b2824876..2855b7ad7d5ae158838dec0e610a2d061702cb94 100644 --- a/base/ca/shared/conf/registry.cfg +++ b/base/ca/shared/conf/registry.cfg @@ -1,5 +1,5 @@ types=profile,defaultPolicy,constraintPolicy,profileInput,profileOutput,profileUpdater -constraintPolicy.ids=noConstraintImpl,subjectNameConstraintImpl,uniqueSubjectNameConstraintImpl,userSubjectNameConstraintImpl,validityConstraintImpl,keyUsageExtConstraintImpl,nsCertTypeExtConstraintImpl,extendedKeyUsageExtConstraintImpl,keyConstraintImpl,basicConstraintsExtConstraintImpl,extensionConstraintImpl,signingAlgConstraintImpl,uniqueKeyConstraintImpl,renewGracePeriodConstraintImpl,authzRealmConstraintImpl +constraintPolicy.ids=noConstraintImpl,subjectNameConstraintImpl,uniqueSubjectNameConstraintImpl,userSubjectNameConstraintImpl,validityConstraintImpl,keyUsageExtConstraintImpl,nsCertTypeExtConstraintImpl,extendedKeyUsageExtConstraintImpl,keyConstraintImpl,basicConstraintsExtConstraintImpl,extensionConstraintImpl,signingAlgConstraintImpl,uniqueKeyConstraintImpl,renewGracePeriodConstraintImpl,authzRealmConstraintImpl,externalProcessConstraintImpl constraintPolicy.signingAlgConstraintImpl.class=com.netscape.cms.profile.constraint.SigningAlgConstraint constraintPolicy.signingAlgConstraintImpl.desc=Signing Algorithm Constraint constraintPolicy.signingAlgConstraintImpl.name=Signing Algorithm Constraint @@ -45,6 +45,9 @@ constraintPolicy.renewGracePeriodConstraintImpl.name=Renewal Grace Period Constr constraintPolicy.uniqueKeyConstraintImpl.class=com.netscape.cms.profile.constraint.UniqueKeyConstraint constraintPolicy.uniqueKeyConstraintImpl.desc=Unique Public Key Constraint constraintPolicy.uniqueKeyConstraintImpl.name=Unique Public Key Constraint +constraintPolicy.externalProcessConstraintImpl.class=com.netscape.cms.profile.constraint.ExternalProcessConstraint +constraintPolicy.externalProcessConstraintImpl.desc=External Process Constraint +constraintPolicy.externalProcessConstraintImpl.name=External Process Constraint defaultPolicy.ids=noDefaultImpl,genericExtDefaultImpl,autoAssignDefaultImpl,subjectNameDefaultImpl,validityDefaultImpl,randomizedValidityDefaultImpl,caValidityDefaultImpl,subjectKeyIdentifierExtDefaultImpl,authorityKeyIdentifierExtDefaultImpl,basicConstraintsExtDefaultImpl,keyUsageExtDefaultImpl,nsCertTypeExtDefaultImpl,extendedKeyUsageExtDefaultImpl,ocspNoCheckExtDefaultImpl,issuerAltNameExtDefaultImpl,subjectAltNameExtDefaultImpl,userSubjectNameDefaultImpl,signingAlgDefaultImpl,userKeyDefaultImpl,userValidityDefaultImpl,userExtensionDefaultImpl,userSigningAlgDefaultImpl,authTokenSubjectNameDefaultImpl,subjectInfoAccessExtDefaultImpl,authInfoAccessExtDefaultImpl,nscCommentExtDefaultImpl,freshestCRLExtDefaultImpl,crlDistributionPointsExtDefaultImpl,policyConstraintsExtDefaultImpl,policyMappingsExtDefaultImpl,nameConstraintsExtDefaultImpl,certificateVersionDefaultImpl,certificatePoliciesExtDefaultImpl,subjectDirAttributesExtDefaultImpl,privateKeyPeriodExtDefaultImpl,inhibitAnyPolicyExtDefaultImpl,imageDefaultImpl,nsTokenDeviceKeySubjectNameDefaultImpl,nsTokenUserKeySubjectNameDefaultImpl,authzRealmDefaultImpl,commonNameToSANDefaultImpl defaultPolicy.autoAssignDefaultImpl.class=com.netscape.cms.profile.def.AutoAssignDefault defaultPolicy.autoAssignDefaultImpl.desc=Auto Request Assignment Default diff --git a/base/server/cms/src/com/netscape/cms/profile/constraint/ExternalProcessConstraint.java b/base/server/cms/src/com/netscape/cms/profile/constraint/ExternalProcessConstraint.java new file mode 100644 index 0000000000000000000000000000000000000000..3ee0d46feea7a56f917621d1ce1fd85bc6b36b7c --- /dev/null +++ b/base/server/cms/src/com/netscape/cms/profile/constraint/ExternalProcessConstraint.java @@ -0,0 +1,158 @@ +// --- 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) 2016, 2017 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +package com.netscape.cms.profile.constraint; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authentication.IAuthToken; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.profile.EProfileException; +import com.netscape.certsrv.profile.ERejectException; +import com.netscape.certsrv.profile.IProfile; +import com.netscape.certsrv.property.Descriptor; +import com.netscape.certsrv.property.IDescriptor; +import com.netscape.certsrv.request.IRequest; +import com.netscape.cms.profile.input.CertReqInput; + +import netscape.security.x509.X509CertInfo; + + +public class ExternalProcessConstraint extends EnrollConstraint { + + public static final String CONFIG_EXECUTABLE = "executable"; + + public static final long DEFAULT_TIMEOUT = 10; + + /* Map of envvars to include, and the corresponding IRequest keys + * + * All keys will be prefixed with "DOGTAG_" when added to environment. + */ + protected static final Map envVars = new TreeMap<>(); + + protected Map extraEnvVars = new TreeMap<>(); + + static { + envVars.put("DOGTAG_CERT_REQUEST", CertReqInput.VAL_CERT_REQUEST); + envVars.put("DOGTAG_USER", + IRequest.AUTH_TOKEN_PREFIX + "." + IAuthToken.USER_ID); + envVars.put("DOGTAG_PROFILE_ID", IRequest.PROFILE_ID); + envVars.put("DOGTAG_AUTHORITY_ID", IRequest.AUTHORITY_ID); + envVars.put("DOGTAG_USER_DATA", IRequest.USER_DATA); + } + + protected String executable; + + public ExternalProcessConstraint() { + addConfigName(CONFIG_EXECUTABLE); + } + + public void init(IProfile profile, IConfigStore config) + throws EProfileException { + super.init(profile, config); + + this.executable = getConfig(CONFIG_EXECUTABLE); + if (this.executable == null || this.executable.isEmpty()) { + throw new EProfileException( + "Missing required config param 'executable'"); + } + + IConfigStore envConfig = config.getSubStore("params.env"); + Enumeration names = envConfig.getPropertyNames(); + while (names.hasMoreElements()) { + String name = names.nextElement(); + try { + extraEnvVars.put(name, envConfig.getString(name)); + } catch (EBaseException e) { + // shouldn't happen; log and move on + CMS.debug( + "ExternalProcessConstraint: caught exception processing " + + "'params.env' config: " + e + ); + + } + } + } + + public IDescriptor getConfigDescriptor(Locale locale, String name) { + if (name.equals(CONFIG_EXECUTABLE)) { + return new Descriptor( + IDescriptor.STRING, null, null, "Executable path"); + } else { + return null; + } + } + + public void validate(IRequest request, X509CertInfo info) + throws ERejectException { + CMS.debug("About to execute command: " + this.executable); + ProcessBuilder pb = new ProcessBuilder(this.executable); + + // set up process environment + Map env = pb.environment(); + for (String k : envVars.keySet()) { + String v = request.getExtDataInString(envVars.get(k)); + if (v != null) + env.put(k, v); + } + for (String k : extraEnvVars.keySet()) { + String v = request.getExtDataInString(extraEnvVars.get(k)); + if (v != null) + env.put(k, v); + } + + Process p; + String stdout = ""; + String stderr = ""; + boolean timedOut; + try { + p = pb.start(); + timedOut = !p.waitFor(DEFAULT_TIMEOUT, TimeUnit.SECONDS); + if (timedOut) + p.destroyForcibly(); + else + stdout = IOUtils.toString(p.getInputStream()); + stderr = IOUtils.toString(p.getErrorStream()); + } catch (Throwable e) { + String msg = + "Caught exception while executing command: " + this.executable; + CMS.debug(msg); + CMS.debug(e); + throw new ERejectException(msg, e); + } + if (timedOut) + throw new ERejectException("Request validation timed out"); + int exitValue = p.exitValue(); + CMS.debug("ExternalProcessConstraint: exit value: " + exitValue); + CMS.debug("ExternalProcessConstraint: stdout: " + stdout); + CMS.debug("ExternalProcessConstraint: stderr: " + stderr); + if (exitValue != 0) + throw new ERejectException(stdout); + } + +} diff --git a/base/server/upgrade/10.4.0/04-AddExternalProcessConstraintToRegistry b/base/server/upgrade/10.4.0/04-AddExternalProcessConstraintToRegistry new file mode 100755 index 0000000000000000000000000000000000000000..a9ee00aece2cd73b135fcd30402d81140bbc086e --- /dev/null +++ b/base/server/upgrade/10.4.0/04-AddExternalProcessConstraintToRegistry @@ -0,0 +1,67 @@ +#!/usr/bin/python +# Authors: +# Fraser Tweedale +# +# 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. +# +# Copyright (C) 2017 Red Hat, Inc. +# All rights reserved. + +from __future__ import absolute_import +import os.path + +import pki +from pki.server.upgrade import PKIServerUpgradeScriptlet + + +class AddExternalProcessConstraintToRegistry(PKIServerUpgradeScriptlet): + + new_config = { + 'constraintPolicy.externalProcessConstraintImpl.class': + 'com.netscape.cms.profile.constraint.ExternalProcessConstraint', + 'constraintPolicy.externalProcessConstraintImpl.desc': + 'External Process Constraint', + 'constraintPolicy.externalProcessConstraintImpl.name': + 'External Process Constraint', + } + + constraint_name = 'externalProcessConstraintImpl' + + def __init__(self): + super(AddExternalProcessConstraintToRegistry, self).__init__() + self.message = 'Add ExternalProcessConstraint to registry' + + def upgrade_subsystem(self, instance, subsystem): + if subsystem.name == 'ca': + self.add_new_entries(instance, subsystem) + + def add_new_entries(self, instance, subsystem): # pylint: disable=W0613 + filename = os.path.join(subsystem.conf_dir, 'registry.cfg') + self.backup(filename) + + properties = pki.PropertyFile(filename) + properties.read() + + # add constraint to constraint list + constraints = properties.get('constraintPolicy.ids').split(',') + if self.constraint_name in constraints: + return # update not required + + constraints.append(self.constraint_name) + properties.set('constraintPolicy.ids', ','.join(constraints)) + + for k, v in self.new_config.items(): + properties.set(k, v) + + properties.write() -- 2.9.3