Skip to content

Commit

Permalink
Support for IPA Sub CAs (#4)
Browse files Browse the repository at this point in the history
* Support for IPA sub CAs/custom issuers
  • Loading branch information
locknut committed Jul 11, 2017
1 parent 6a28da7 commit 639abab
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 27 deletions.
29 changes: 19 additions & 10 deletions README.md
@@ -1,13 +1,11 @@
[![Puppet
Forge](http://img.shields.io/puppetforge/v/saltedsignal/certmonger.svg)](https://forge.puppetlabs.com/saltedsignal/certmonger)
[![Puppet Forge](http://img.shields.io/puppetforge/v/saltedsignal/certmonger.svg)](https://forge.puppetlabs.com/saltedsignal/certmonger)
[![Build Status](https://travis-ci.org/saltedsignal/puppet-certmonger.svg?branch=master)](https://travis-ci.org/saltedsignal/puppet-certmonger)

# Certmonger puppet module

This puppet modules enable some resources and providers that you can use to
request certificates using certmonger.
This puppet module allows you to request and manage certificates using certmonger.

## Request a certificate from IPA using a puppet define
## Request a certificate from IPA using the defined type

### Simple usage:

Expand Down Expand Up @@ -44,7 +42,8 @@ Note: there is no need to use the `certmonger` class, it gets included by the de
* `presavecmd` (optional; String) - Command certmonger should run before saving the certificate
* `postsavecmd` (optional; String) - Command certmonger should run after saving the certificate
* `profile` (optional; String) - Ask the CA to process request using the named profile. e.g. `caIPAserviceCert`

* `issuer` (optional; String) - Ask the CA to process the request using the named issuer. e.g. `ca-puppet`
* `issuerdn` (optional; String) - If a specific issuer is needed, provide the issuer DN. e.g. `CN=Puppet CA`

### Actions:
* Submits a certificate request to an IPA server for a new certificate via `ipa-getcert` utility
Expand All @@ -56,6 +55,7 @@ Note: there is no need to use the `certmonger` class, it gets included by the de

### Fixing file/folder permissions after certificate issuance
A notable limitation of `ipa-getcert` is that the `postsavecmd` can only take a single command. This means changing file ownership/modes and restarting services requires the use of a separate helper utility. This module includes a creatively named script called `change-perms-restart`, which gets installed by the `certmonger` class as `/usr/local/bin/change-perms-restart`. Usage is as follows:

```
/usr/local/bin/change-perms-restart [ -R] [ -r 'service1 service2' ] [ -t 'service3 service4' ] [ -s facility.severity ] owner:group:modes:/path/to/file [ ... ]
Expand All @@ -64,14 +64,16 @@ A notable limitation of `ipa-getcert` is that the `postsavecmd` can only take a
-t space separated list of services to restart via systemctl
-s log output (if any) to syslog with specified facility/severity
```

For example: `change-perms-restart -R -s daemon.notice -r 'httpd rsyslog' -t 'postfix postgresql' root:pkiuser:0644:/etc/pki/tls/certs/localhost.crt root:pkiuser:0600:/etc/pki/tls/private/localhost.key`

### Other limitations:
* The current state is determined by calling a custom shell script (supplied). Not ideal, I know.
* Only supports file-based certificates (i.e. no support for NSSDB).
* Does not manage the nickname, IP address, email, etc features.
* Only manages subject, dns (subjectAltNames), key usage, eku, principal, pre/post save commands.
* Only manages subject, dns (subjectAltNames), key usage, eku, principal, issuer, pre/post save commands.
* Only manages the principal if it appears in the issued certificate - which depends on your CA profile.
* Once a certificate is issued, this module can't manage the profile because it doesn't appear in the issued certificate.
* Subject is hardcoded to `CN=$hostname`.
* Only works if being run on a system already joined to an IPA domain, and only works against IPA CAs.
* If you specify a hostname and don't specify a principal, this module will assume you want `host/$hostname`.
Expand All @@ -90,7 +92,7 @@ For example: `change-perms-restart -R -s daemon.notice -r 'httpd rsyslog' -t 'p

```puppet
certmonger::request_ipa_cert {'webserver-certificate':
server => "${fqdn}",
hostname => "${fqdn}",
principal => "HTTP/${fqdn}",
keyfile => "/etc/pki/tls/private/server.key",
certfile => "/etc/pki/tls/certs/server.crt",
Expand All @@ -99,7 +101,7 @@ For example: `change-perms-restart -R -s daemon.notice -r 'httpd rsyslog' -t 'p
}
```

## Request a certificate from IPA using the certmonger provider
## Request a certificate using the native type/provider

This will create a certificate request with the given hostname (which will be
used in the subject as the CN) and the given principal. It will use the key
Expand Down Expand Up @@ -145,6 +147,10 @@ errors can be ignored with the 'ignore_ca_errors' parameter.
One can also automatically stop tracking the certificate request if it's
rejected by the CA. This is done by setting the 'cleanup_on_error' flag.

### Limitations
* The native type/provider isn't as mature as the defined type, which means its parameters/properties are likely change in a non-backward compatible way.
* The defined type is very mature - its parameters are unlikely to change, and if they do, those changes will be backward-compatible.
* If you're using IPA and are having trouble with the native type/provider, try switching to the defined type.

## Contributing
* Fork it
Expand All @@ -153,7 +159,10 @@ rejected by the CA. This is done by setting the 'cleanup_on_error' flag.
* Submit a PR

## Acknowledgements
Honorable mention goes out to:

This module is brought to you by [Salted Signal](https://www.saltedsignal.com.au) - a Melbourne-based cloud automation, security and compliance company.

Honorable mentions go out to:
* Rob Crittenden for his work on https://github.com/rcritten/puppet-certmonger, which was used as inspiration for this module.
* Juan Antonio Osorio for his work on the certmonger type/provider and setting up rpsec tests/travis-ci integration.
* Alex J Fisher for fixing rubocop violations
15 changes: 14 additions & 1 deletion files/verify_certmonger_request.sh
@@ -1,6 +1,6 @@
#!/bin/bash

while getopts ":f:k:N:K:D:u:U:B:C:w:" opt; do
while getopts ":f:k:N:K:D:u:U:X:B:C:w:" opt; do
case "$opt" in
f) certfile="$OPTARG"
;;
Expand All @@ -16,6 +16,8 @@ while getopts ":f:k:N:K:D:u:U:B:C:w:" opt; do
;;
U) eku="$OPTARG"
;;
X) issuer="$OPTARG"
;;
B) presavecmd="$OPTARG"
;;
C) postsavecmd="$OPTARG"
Expand Down Expand Up @@ -159,4 +161,15 @@ if echo "$output" | grep -q '\s*eku:' && [ -n "$eku" ]; then

elif [ -n "$eku" ]; then
exit 9
fi

# Is the expected issuer the same as whats already in the certrequest?
if echo "$output" | grep -q '^\s*issuer:' && [ -n "$issuer" ]; then
# take output of ipa-getcert list | grep issuer: | strip off the 'issuer:' part
output_issuer="$(echo "$output" | grep '^\s*issuer:' | sed -e 's/^\s*issuer:\s*//g')"
if [ "$output_issuer" != "$issuer" ]; then
exit 10
fi
elif [ -n "$issuer" ]; then
exit 10
fi
40 changes: 25 additions & 15 deletions manifests/request_ipa_cert.pp
Expand Up @@ -25,6 +25,8 @@
# * `presavecmd` (optional; String) - Command certmonger should run before saving the certificate
# * `postsavecmd` (optional; String) - Command certmonger should run after saving the certificate
# * `profile` (optional; String) - Ask the CA to process request using the named profile. e.g. `caIPAserviceCert`
# * `issuer` (optional; String) - Ask the CA to process the request using the named issuer. e.g. `ca-puppet`
# * `issuerdn` (optional; String) - If a specific issuer is needed, provide the issuer DN. e.g. `CN=Puppet CA`
#
define certmonger::request_ipa_cert (
$certfile,
Expand All @@ -37,6 +39,8 @@
$presavecmd = undef,
$postsavecmd = undef,
$profile = undef,
$issuer = undef,
$issuerdn = undef,
) {
include ::certmonger
include ::certmonger::scripts
Expand Down Expand Up @@ -121,13 +125,27 @@
if $presavecmd { $options_presavecmd = "-B '${presavecmd}'" } else { $options_presavecmd = '' }
if $postsavecmd { $options_postsavecmd = "-C '${postsavecmd}'" } else { $options_postsavecmd = '' }
if $profile { $options_profile = "-T '${profile}'" } else { $options_profile = '' }
if $issuer {
$options_issuer = "-X '${issuer}'"
if $issuerdn {
$options_issuerdn = "-X '${issuerdn}'"
} else {
fail('certmonger::request_ipa_cert: issuerdn is required if issuer is specified.')
}
} else {
$options_issuer = ''
$options_issuerdn = ''
}

$request_attrib_options = "${options_subject} ${options_principal} ${options_dns} \
${options_usage} ${options_eku} ${options_issuer} ${options_profile} ${options_presavecmd} ${options_postsavecmd}"
$verify_attrib_options = "${options_subject} ${options_principal} ${options_dns_csv} \
${options_usage_csv} ${options_eku_csv} ${options_issuerdn} ${options_presavecmd} ${options_postsavecmd}"

exec { "ipa-getcert-${certfile}-trigger":
path => '/usr/bin:/bin',
command => '/bin/true',
unless => "${::certmonger::scripts::verifyscript} ${options} ${options_subject} ${options_principal} \
${options_dns_csv} ${options_usage_csv} ${options_eku_csv} \
${options_presavecmd} ${options_postsavecmd}",
unless => "${::certmonger::scripts::verifyscript} ${options} ${verify_attrib_options}",
onlyif => '/usr/bin/test -s /etc/ipa/default.conf',
require => [Service['certmonger'], File[$::certmonger::scripts::verifyscript]],
notify => [Exec["ipa-getcert-request-${certfile}"],Exec["ipa-getcert-resubmit-${certfile}"]],
Expand All @@ -139,9 +157,7 @@
provider => 'shell',
command => "rm -rf ${keyfile} ${certfile} ; mkdir -p `dirname ${keyfile}` `dirname ${certfile}` ;
ipa-getcert stop-tracking ${options_certfile} ;
ipa-getcert request ${options} ${options_subject} ${options_principal} \
${options_dns} ${options_usage} ${options_eku} \
${options_presavecmd} ${options_postsavecmd} ${options_profile}",
ipa-getcert request ${options} ${request_attrib_options}",
unless => "${::certmonger::scripts::verifyscript} ${options}",
notify => Exec["ipa-getcert-${certfile}-verify"],
require => [Service['certmonger'],File[$::certmonger::scripts::verifyscript]],
Expand All @@ -151,12 +167,8 @@
refreshonly => true,
path => '/usr/bin:/bin',
provider => 'shell',
command => "ipa-getcert resubmit ${options_certfile} ${options_subject} ${options_principal} \
${options_dns} ${options_usage} ${options_eku} \
${options_presavecmd} ${options_postsavecmd} ${options_profile}",
unless => "${::certmonger::scripts::verifyscript} ${options_certfile} ${options_subject} ${options_principal} \
${options_dns_csv} ${options_usage_csv} ${options_eku_csv} \
${options_presavecmd} ${options_postsavecmd}",
command => "ipa-getcert resubmit ${options_certfile} ${request_attrib_options}",
unless => "${::certmonger::scripts::verifyscript} ${options_certfile} ${verify_attrib_options}",
onlyif => ["${::certmonger::scripts::verifyscript} ${options}","openssl x509 -in ${certfile} -noout"],
notify => Exec["ipa-getcert-${certfile}-verify"],
require => [Service['certmonger'], File[$::certmonger::scripts::verifyscript]],
Expand All @@ -165,9 +177,7 @@
exec {"ipa-getcert-${certfile}-verify":
refreshonly => true,
path => '/usr/bin:/bin',
command => "${::certmonger::scripts::verifyscript} ${options} ${options_subject} ${options_principal} -w 8 \
${options_dns_csv} ${options_usage_csv} ${options_eku_csv} \
${options_presavecmd} ${options_postsavecmd}",
command => "${::certmonger::scripts::verifyscript} -w 8 ${options} ${verify_attrib_options}",
require => [Service['certmonger'],File[$::certmonger::scripts::verifyscript]],
}

Expand Down
2 changes: 1 addition & 1 deletion metadata.json
@@ -1,6 +1,6 @@
{
"name": "saltedsignal-certmonger",
"version": "2.0.0",
"version": "2.1.0",
"author": "saltedsignal",
"summary": "Certmonger puppet module",
"license": "Apache-2.0",
Expand Down

0 comments on commit 639abab

Please sign in to comment.