CSR attributes and certificate extensions

When Puppet agent nodes request their certificates, the certificate signing request (CSR) usually contains only their certname and the necessary cryptographic information. Agents can also embed additional data in their CSR, useful for policy-based autosigning and for adding new trusted facts.

Embedding additional data into CSRs is useful when:
  • Large numbers of nodes are regularly created and destroyed as part of an elastic scaling system.

  • You are willing to build custom tooling to make certificate autosigning more secure and useful.

It might also be useful in deployments where Puppet is used to deploy private keys or other sensitive information, and you want extra control over nodes that receive this data.

If your deployment doesn’t match one of these descriptions, you might not need this feature.

Timing: When data can be added to CSRs and certificates

When Puppet agent starts the process of requesting a catalog, it checks whether it has a valid signed certificate. If it does not, it generates a key pair, crafts a CSR, and submits it to the certificate authority (CA) Puppet Server. For detailed information, see agent/server HTTPS traffic.

For practical purposes, a certificate is locked and immutable as soon as it is signed. For data to persist in the certificate, it has to be added to the CSR before the CA signs the certificate.

This means any desired extra data must be present before Puppet agent attempts to request its catalog for the first time.

Populate any extra data when provisioning the node. If you make an error, see the Troubleshooting section below for information about recovering from failed data embedding.

Data location and format

Extra data for the CSR is read from the csr_attributes.yaml file in Puppet's confdir. The location of this file can be changed with the csr_attributes configuration setting.

The csr_attributes.yaml file must contain a YAML hash with one or both of the following keys:
  • custom_attributes

  • extension_requests

The value of each key must also be a hash, where:
  • Each key is a valid object identifier (OID) Puppet-specific OIDscan optionally be referenced by short name instead of by numeric ID.

  • Each value is an object that can be cast to a string — numbers are allowed but arrays are not.

For information about how each hash is used and recommended OIDs for each hash, see the sections below.

Custom attributes (transient CSR data)

Custom attributes are pieces of data that are embedded only in the CSR. The CA can use them when deciding whether to sign the certificate, but they are discarded after that and aren’t transferred to the final certificate.

Default behavior

The puppetserver ca list command doesn’t display custom attributes for pending CSRs, and basic autosigning (autosign.conf) doesn’t check them before signing.

Configurable behavior

If you use policy-based autosigning your policy executable receives the complete CSR in PEM format. The executable can extract and inspect the custom attributes, and use them to decide whether to sign the certificate.

The simplest method is to embed a pre-shared key of some kind in the custom attributes. A policy executable can compare it to a list of known keys and autosign certificates for any pre-authorized nodes.

A more complex use might be to embed an instance-specific ID and write a policy executable that can check it against a list of your recently requested instances on a public cloud, like EC2 or GCE.

Manually checking for custom attributes in CSRs

You can check for custom attributes by using OpenSSL to dump a CSR in pem format to text format, by running this command:
openssl req -noout -text -in <name>.pem
In the output, look for the Attributes section which appears below the Subject Public Key Info block:
Attributes:
    challengePassword        :342thbjkt82094y0uthhor289jnqthpc2290

Extension requests (permanent certificate data)

Extension requests are pieces of data that are transferred as extensions to the final certificate, when the CA signs the CSR. They persist as trusted, immutable data, that cannot be altered after the certificate is signed.

They can also be used by the CA when deciding whether or not to sign the certificate.

Default behavior

When signing a certificate, Puppet’s CA tools transfer any extension requests into the final certificate.

You can access certificate extensions in manifests as $trusted["extensions"]["<EXTENSION OID>"].

Select OIDs in the ppRegCertExt and ppAuthCertExt ranges. See the Puppet-specific Registered IDs. By default, any other OIDs appear as plain dotted numbers, but you can use the custom_trusted_oid_mapping.yaml file to assign short names to any other OIDs you use at your site. If you do, those OIDs appear in $trusted as their short names, instead of their full numerical OID.

For more information about $trusted, see Facts and built-in variables.

The visibility of extensions is limited:

Puppet’s authorization system (auth.conf) does not use certificate extensions, but Puppet Server’s authorization system, which is based on trapperkeeper-authorization, can use extensions in the ppAuthCertExt OID range, and requires them for requests to write access rules.

Configurable behavior

If you use policy-based autosigning, your policy executable receives the complete CSR in pem format. The executable can extract and inspect the extension requests, and use them when deciding whether to sign the certificate.

Manually checking for extensions in CSRs and certificates

You can check for extension requests in a CSR by running the OpenSSL command to dump a CSR in pem format to text format:

openssl req -noout -text -in <name>.pem

In the output, look for a section called Requested Extensions, which appears below the Subject Public Key Info and Attributes blocks:

Requested Extensions:
    pp_uuid:
    .$ED803750-E3C7-44F5-BB08-41A04433FE2E
    1.3.6.1.4.1.34380.1.1.3:
    ..my_ami_image
    1.3.6.1.4.1.34380.1.1.4:
    .$342thbjkt82094y0uthhor289jnqthpc2290
Note: Every extension is preceded by any combination of two characters (.$ and .. in the example above) that contain ASN.1 encoding information. Because OpenSSL is unaware of Puppet’s custom extensions OIDs, it’s unable to properly display the values.

Any Puppet-specific OIDs (see below) appear as numeric strings when using OpenSSL.

You can check for extensions in a signed certificate by running:

/opt/puppetlabs/puppet/bin/openssl x509 -noout -text -in $(puppet config print signeddir)/<certname>.pem

In the output, look for the X509v3 extensions section. Any of the Puppet-specific registered OIDs appear as their descriptive names:

X509v3 extensions:
    Netscape Comment:
        Puppet Ruby/OpenSSL Internal Certificate
    X509v3 Subject Key Identifier:
        47:BC:D5:14:33:F2:ED:85:B9:52:FD:A2:EA:E4:CC:00:7F:7F:19:7E
    Puppet Node UUID:
        ED803750-E3C7-44F5-BB08-41A04433FE2E
    X509v3 Extended Key Usage: critical
        TLS Web Server Authentication, TLS Web Client Authentication
    X509v3 Basic Constraints: critical
        CA:FALSE
    Puppet Node Preshared Key:
        342thbjkt82094y0uthhor289jnqthpc2290
    X509v3 Key Usage: critical
        Digital Signature, Key Encipherment
    Puppet Node Image Name:
        my_ami_image

Puppet-specific registered IDs

ppRegCertExt

The ppRegCertExt OID range contains the following OIDs as reserved names to use as values when signing trusted certificates:

Numeric ID Short name Descriptive name
1.3.6.1.4.1.34380.1.1.1 pp_uuid Puppet node UUID
1.3.6.1.4.1.34380.1.1.2 pp_instance_id Puppet node instance ID
1.3.6.1.4.1.34380.1.1.3 pp_image_name Puppet node image name
1.3.6.1.4.1.34380.1.1.4 pp_preshared_key Puppet node preshared key
1.3.6.1.4.1.34380.1.1.5 pp_cost_center Puppet node cost center name
1.3.6.1.4.1.34380.1.1.6 pp_product Puppet node product name
1.3.6.1.4.1.34380.1.1.7 pp_project Puppet node project name
1.3.6.1.4.1.34380.1.1.8 pp_application Puppet node application name
1.3.6.1.4.1.34380.1.1.9 pp_service Puppet node service name
1.3.6.1.4.1.34380.1.1.10 pp_employee Puppet node employee name
1.3.6.1.4.1.34380.1.1.11 pp_created_by Puppet node created_by tag
1.3.6.1.4.1.34380.1.1.12 pp_environment Puppet node environment name
1.3.6.1.4.1.34380.1.1.13 pp_role Puppet node role name
1.3.6.1.4.1.34380.1.1.14 pp_software_version Puppet node software version
1.3.6.1.4.1.34380.1.1.15 pp_department Puppet node department name
1.3.6.1.4.1.34380.1.1.16 pp_cluster Puppet node cluster name
1.3.6.1.4.1.34380.1.1.17 pp_provisioner Puppet node provisioner name
1.3.6.1.4.1.34380.1.1.18 pp_region Puppet node region name
1.3.6.1.4.1.34380.1.1.19 pp_datacenter Puppet node datacenter name
1.3.6.1.4.1.34380.1.1.20 pp_zone Puppet node zone name
1.3.6.1.4.1.34380.1.1.21 pp_network Puppet node network name
1.3.6.1.4.1.34380.1.1.22 pp_securitypolicy Puppet node security policy name
1.3.6.1.4.1.34380.1.1.23 pp_cloudplatform Puppet node cloud platform name
1.3.6.1.4.1.34380.1.1.24 pp_apptier Puppet node application tier
1.3.6.1.4.1.34380.1.1.25 pp_hostname Puppet node hostname

ppAuthCertExt

The ppAuthCertExt OID range contains the following OIDs:
Numeric ID Short name Descriptive name
1.3.6.1.4.1.34380.1.3.1 pp_authorization Certificate extension authorization
1.3.6.1.4.1.34380.1.3.13 pp_auth_role Puppet node role name for authorization. For PE internal use only.

Cloud provider attributes and extensions population example

To populate the csr_attributes.yaml file when you provision a node, use an automated script such as cloud-init.

For example, when provisioning a new node from the AWS EC2 dashboard, enter the following script into the Configure Instance Details —> Advanced Details section:

#!/bin/sh
if [ ! -d /etc/puppetlabs/puppet ]; then
   mkdir /etc/puppetlabs/puppet
fi
cat > /etc/puppetlabs/puppet/csr_attributes.yaml << YAML
custom_attributes:
    1.2.840.113549.1.9.7: mySuperAwesomePassword
extension_requests:
    pp_instance_id: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)
    pp_image_name:  $(curl -s http://169.254.169.254/latest/meta-data/ami-id)
YAML

This populates the attributes file with the AWS instance ID, image name, and a pre-shared key to use with policy-based autosigning.

Troubleshooting

Recovering from failed data embedding

When testing this feature for the first time, you might not embed the right information in a CSR, or certificate, and might want to start over for your test nodes. This is not really a problem after your provisioning system is changed to populate the data, but it can easily happen when doing things manually.

To start over, do the following.

On the test node:
  • Turn off Puppet agent, if it’s running.

  • If using Puppet version 6.0.3 or greater, run puppet ssl clean. If not, delete the following files:

    • $ssldir/certificate_requests/<name>.pem

    • $ssldir/certs/<name>.pem

On the CA primary Puppet server:
  • Check whether a signed certificate exists. Use puppetserver ca list --all to see the complete list. If it exists, revoke and delete it with puppetserver ca clean --certname <name>.

After you’ve done that, you can start over.