Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CA hardening and status URI change support #105

Merged
merged 10 commits into from
Apr 25, 2017
13 changes: 13 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ Added
- Add custom pre and post task hooks to allow more flexibility with PKI
management. [muelli_]

- Support to change or disable CRL and OCSP for PKI authorities using
``item.crl`` and ``item.ocsp``. [ypid_]

- Use X.509 Name Constraints to limit PKI authorities to ``item.domain`` by default.
This greatly reduces the damage that a compromised PKI authority could do
(which is trusted by the cluster by default).
Previously, any CA managed by ``debops.pki`` could happily issue certificates
for any domain and clients would accept them which is probably not what you want.
Use ``item.name_constraints`` if you want to change the default.
Note that this new default is only effective for newly created CAs.
Refer to `A Web PKI x509 certificate primer <https://developer.mozilla.org/en-US/docs/Mozilla/Security/x509_Certificates>`_
for details. [ypid_]


`debops.pki v0.2.14`_ - 2016-11-21
----------------------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/acme-integration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Names`_), which does not include the ``example.com`` apex domain.
acme_default_subdomains: []
# Can also include different domains like 'mail.example.org'
# in the same realm.
acme_domains: [ 'logs.example.com', 'mon.example.com' ]
acme_domains: [ 'mon.example.com' ]
# acme_ca: 'le-staging'

ACME configuration variables
Expand Down
31 changes: 31 additions & 0 deletions docs/defaults-detailed.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,34 @@ respectively:
- 'uri:https://{{ ansible_domain }}/'
- 'dns:*.{{ ansible_domain }}'
- 'dns:{{ ansible_domain }}'

pki_authorities
---------------
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that there was/is no documentation for pki_authorities at all. My bad, thanks for starting it. It will need to be expanded upon later.


The set of :envvar:`pki_authorities` lists can be used to define internal
Certificate Authorities managed on an Ansible Controller.

List of supported parameters (incomplete):

``crl``
The CRL URL to include in certificates which can be used for certificate
status checking. The default is ``True`` which will result in ``http://\$name.\$domain_suffix/crl/``.
It can be set to ``False`` to not include a CRL URL in certificates.
Any other value (not matching :regexp:`^(?:[Tt]rue|[Ff]alse)$`) will be included as is as CRL URL.

``ocsp``
The OCSP URL to include in certificates which can be used for certificate
status checking. The default is ``True`` which will result in ``http://\$name.\$domain_suffix/ocsp/``.
It can be set to ``False`` to not include a OCSP URL in certificates.
Any other value (not matching :regexp:`^(?:[Tt]rue|[Ff]alse)$`) will be included as is as OCSP URL.

``name_constraints``
The X.509 Name Constraints certificate extension to include in certificates
which will be used during certificate verification to ensure that the CA is
authorized to issue a certificate for the name in question.
The extension is set to critical which REQUIRES X.509 libraries to support it or to return an error.
This is done following common recommendations
(ref: `Which properties of a X.509 certificate should be critical and which not? <https://security.stackexchange.com/questions/30974/which-properties-of-a-x-509-certificate-should-be-critical-and-which-not>`_).
The default is ``True`` which will result in ``critical, permitted;DNS:${config_domain}``.
It can be set to ``False`` to not include X.509 Name Constraints in certificates.
Any other value (not matching :regexp:`^(?:[Tt]rue|[Ff]alse)$`) will be included as is as X.509 Name Constraint.
108 changes: 98 additions & 10 deletions files/secret/pki/lib/pki-authority
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/env bash
# vim: foldmarker={{{,}}}:foldmethod=marker

# pki-authority: CA-side PKI management
# Copyright (C) 2016 Maciej Delmanowski <[email protected]>
# Copyright (C) 2016-2017 Robin Schneider <[email protected]>
# Homepage: https://debops.org/

set -o nounset -o pipefail -o errexit
Expand Down Expand Up @@ -106,6 +108,57 @@ chgrp_idempotent () {
}
# }}}

get_openssl_ocsp_uri_directive () {
local ocsp_uri
case "${config['ocsp']}" in
true|True)
ocsp_uri="OCSP;URI.0 = \$ocsp_url"
;;
false|False)
ocsp_uri=""
;;
*)
ocsp_uri="OCSP;URI.0 = ${config['ocsp']}"
;;
esac
echo "$ocsp_uri"
}

get_openssl_name_constraints_directive () {
local config_domain="${1}"
local name_constraints
case "${config['name_constraints']}" in
true|True)
name_constraints="nameConstraints = critical, permitted;DNS:${config_domain}"
;;
false|False)
name_constraints=""
;;
*)
name_constraints="nameConstraints = ${config['name_constraints']}"
;;
esac

echo "$name_constraints"
}

get_openssl_crl_distribution_points_directive () {
local crl_distribution_points=''
case "${config['crl']}" in
true|True)
crl_distribution_points="crlDistributionPoints = @crl_info"
;;
false|False)
crl_distribution_points=""
;;
*)
crl_distribution_points="crlDistributionPoints = ${config['crl']}"
;;
esac

echo "$crl_distribution_points"
}

initialize_environment () {

declare -gA config
Expand All @@ -126,6 +179,7 @@ initialize_environment () {
config["ca_type"]=""
config["issuer_name"]=""
config["alt_authority"]=""
config["name_constraints"]=""

config["pki_default_sign_base"]="365"
config["pki_default_root_sign_multiplier"]="12"
Expand All @@ -136,6 +190,8 @@ initialize_environment () {
config["ca_sign_days"]=""
config["cert_sign_days"]=""
config["key_size"]="4096"
config["crl"]="true"
config["ocsp"]="true"

config["public_dir_group"]="$(id -g)"
config["public_file_group"]="$(id -g)"
Expand Down Expand Up @@ -165,8 +221,7 @@ enter_authority () {

if [ -r "${config_file}" ] ; then

# FIXME: Add a code that checks if the config file has no dangerous code inside
# shellcheck disable=SC1090
# shellcheck source=/dev/null
. "${config_file}"
fi

Expand Down Expand Up @@ -259,6 +314,8 @@ create_openssl_selfsign_config () {
local config_ca_type="${4}"

if [ -n "${config_file}" ] && [ ! -r "${config_file}" ] ; then
local ocsp_uri
ocsp_uri="$(get_openssl_ocsp_uri_directive)"

cat << EOF > "${config_file}"
# Configuration file generated by pki-authority
Expand Down Expand Up @@ -294,7 +351,7 @@ URI.0 = \$crl_url

[ issuer_info ]
caIssuers;URI.0 = \$aia_url
OCSP;URI.0 = \$ocsp_url
${ocsp_uri}

[ extension_ocsp ]
authorityKeyIdentifier = keyid:always
Expand All @@ -314,20 +371,26 @@ emailAddress = optional
[ extension_default ]
EOF

local name_constraints=''
name_constraints="$(get_openssl_name_constraints_directive "$config_domain")"
local crl_distribution_points=''
crl_distribution_points="$(get_openssl_crl_distribution_points_directive)"
if [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
cat << EOF >> "${config_file}"
basicConstraints = critical, CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
${name_constraints}

EOF
elif [ "${config_ca_type}" = "service" ] ; then
cat << EOF >> "${config_file}"
authorityInfoAccess = @issuer_info
basicConstraints = critical, CA:TRUE, pathlen:0
crlDistributionPoints = @crl_info
${crl_distribution_points}
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
${name_constraints}

EOF
fi
Expand Down Expand Up @@ -384,6 +447,8 @@ create_openssl_sign_config () {
local config_issuer="${5}"

if [ -n "${config_file}" ] && [ ! -r "${config_file}" ] ; then
local ocsp_uri
ocsp_uri="$(get_openssl_ocsp_uri_directive)"

cat << EOF > "${config_file}"
# Configuration file generated by pki-authority
Expand Down Expand Up @@ -443,7 +508,7 @@ URI.0 = \$crl_url

[ issuer_info ]
caIssuers;URI.0 = \$aia_url
OCSP;URI.0 = \$ocsp_url
${ocsp_uri}

[ extension_ocsp ]
authorityKeyIdentifier = keyid:always
Expand Down Expand Up @@ -489,15 +554,20 @@ emailAddress = optional
EOF
fi

local name_constraints=''
name_constraints="$(get_openssl_name_constraints_directive "$config_domain")"
local crl_distribution_points=''
crl_distribution_points="$(get_openssl_crl_distribution_points_directive)"
if [ -z "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
cat << EOF >> "${config_file}"
[ extension_default ]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical, CA:TRUE, pathlen:0
crlDistributionPoints = @crl_info
${crl_distribution_points}
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
${name_constraints}

EOF
elif [ -z "${config_issuer}" ] && [ "${config_ca_type}" = "service" ] ; then
Expand All @@ -506,7 +576,7 @@ EOF
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always, issuer:always
basicConstraints = critical, CA:FALSE
crlDistributionPoints = @crl_info
${crl_distribution_points}
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectKeyIdentifier = hash
Expand All @@ -518,7 +588,7 @@ EOF
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always, issuer:always
basicConstraints = critical, CA:FALSE
crlDistributionPoints = @crl_info
${crl_distribution_points}
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectKeyIdentifier = hash
Expand Down Expand Up @@ -1157,7 +1227,7 @@ sub_sign () {

if key_exists args "name" ; then

enter_authority ${args['name']}
enter_authority "${args['name']}"

local library="${config['pki_library']}"
local input
Expand Down Expand Up @@ -1322,6 +1392,24 @@ sub_new-ca () {
key-size=*)
args["key_size"]=${OPTARG#*=}
;;
crl)
args["crl"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
;;
crl=*)
args["crl"]=${OPTARG#*=}
;;
ocsp)
args["ocsp"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
;;
ocsp=*)
args["ocsp"]=${OPTARG#*=}
;;
name-constraints)
args["name_constraints"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
;;
name-constraints=*)
args["name_constraints"]=${OPTARG#*=}
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
Expand All @@ -1346,7 +1434,7 @@ sub_new-ca () {

if key_exists args "name" ; then

enter_authority ${args['name']}
enter_authority "${args['name']}"

local config_changed="false"

Expand Down
5 changes: 3 additions & 2 deletions files/usr/local/lib/pki/pki-realm
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/env bash
# vim: foldmarker={{{,}}}:foldmethod=marker

# pki-realm: client-side PKI management
# Copyright (C) 2016 Maciej Delmanowski <[email protected]>
# Copyright (C) 2016-2017 Robin Schneider <[email protected]>
# Homepage: https://debops.org/

set -o nounset -o pipefail -o errexit
Expand Down Expand Up @@ -388,8 +390,7 @@ enter_realm () {

if [ -r "${config_file}" ] ; then

# FIXME: Add a code that checks if the config file has no dangerous code inside
# shellcheck disable=SC1090
# shellcheck source=/dev/null
. "${config_file}"
fi

Expand Down
6 changes: 4 additions & 2 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,7 @@
environment:
PKI_ROOT: '{{ secret + "/pki" }}'
PKI_LIBRARY: '{{ item.pki_ca_library | d(pki_ca_library) }}'
PKI_CA_CERTIFICATES: '{{ secret + "/pki/ca-certificates/" +
(item.ca_certificates_path | d(pki_ca_certificates_path)) }}'
PKI_CA_CERTIFICATES: '{{ secret + "/pki/ca-certificates/" + (item.ca_certificates_path | d(pki_ca_certificates_path)) }}'
command: ./lib/pki-authority init --name "{{ item.name }}"
--default-sign-base "{{ pki_default_sign_base }}"
--root-sign-multiplier "{{ pki_default_root_sign_multiplier }}"
Expand Down Expand Up @@ -340,6 +339,9 @@
--system-ca "{{ (item.system_ca | d(True)) | bool | lower }}"
--alt-authority "{{ item.alt_authority | d('') }}"
--key-size "{{ item.key_size | d('') }}"
--crl "{{ item.crl | d(True) }}"
--ocsp "{{ item.ocsp | d(True) }}"
--name-constraints "{{ item.name_constraints | d(True) }}"
args:
chdir: '{{ secret + "/pki" }}'
creates: '{{ secret + "/pki/authorities/" + item.name + "/subject/cert.pem" }}'
Expand Down