Policy-based autosigning in Azure with the CLI and virtual machine tags

When developing a Puppet-based solution that uses a Puppet master, one of the initial challenges you need to solve is how your agent certificates are going to be signed. To quote the Puppet documentation:

Before Puppet agent nodes can retrieve their configuration catalogs, they need a signed certificate from the local Puppet certificate authority (CA). When using Puppet’s built-in CA , agents will submit a certificate signing request (CSR) to the CA Puppet master and will retrieve a signed certificate once one is available.

You need to take care when it comes to certificate signing, as it provides one of the core layers of defence against rogue or unauthorized Puppet agents requesting catalogs from your master. This is an issue because catalogs often contain secrets or other information about your infrastructure that should be exposed only to authorized nodes.

There are a number of ways to sign incoming certificate requests:

  • Interactively

  • Programatically

  • Automatically

Policy-based auto-signing in Microsoft Azure environments

In this use case, we will demonstrate how policy-based autosigning can be used to automatically sign Puppet certificates from virtual machines residing in Microsoft Azure after validating that the virtual machine instance meets all the environment’s standards.

Environment overview

In this demo environment, we provision Windows and Linux instances via Microsoft Azure Resource Manager templates through a CI/CD pipeline. Provisioned virtual machine instances reside in resource groups that match their short (non-fqdn) name.

As every instance provisioned has its Standard Operating Environment (SOE) configured and managed via Puppet, we inject the Puppet agent using a custom script extension on launch, which results in a CSR to the Puppet master on first run. As this is a fully automated environment, we must have the CSR signed as soon as possible. However, before signing the certificate, we want to ensure a few things are in place to ensure there is no degradation to the security of the environment.

Specifically, we want to ensure that:

  1. The instance's certificate matches the defined hostname standard for the environment.
  2. The instance resides in our Azure subscription.
  3. The instance has been tagged correctly as per the tagging standards for the environments.

Fortunately, Puppet's policy-based autosigning functionality allows us to execute a script each time a CSR is received. That lets us evaluate our three requirements above, and sign the certificate if it is deemed suitable.

Programmatically querying Azure resources

Azure comes with a rich API and easy-to-use command line tool that allows you to interact with all the resources within your Azure subscriptions. In this case, we'll leverage the CLI to query our subscription's resources directly and use the --json flag to return the response to us in JSON for parsing.

Installing and configuring the Azure CLI against your subscription is out of scope for this post. However, please ensure that:

  • The Azure CLI is configured to to be executed by the user who runs the Puppet server (i.e., pe-puppet), as policy-based autosigning scripts are executed by this user. In other words, ensure that azure login is run by this user.
  • The Azure CLI user has read-only access to your subscription resources. We need only to be able to query the subscription resources, not change them. Follow the least-privilege model.
  • The Azure CLI is configured to use ARM mode.

We could have used Azure's REST API or Azure's Ruby SDK for this, which would have eliminated the need for installing the Azure CLI. However, for the purpose of demonstration, using the Azure CLI is the path of least resistance.

Using policy-based autosigning

Policy-based autosigning functions as follows:

  1. Define a script or binary to be executed by the Puppet CA on receipt of a CSR in the main section of the puppet.conf file.
  2. On receipt of the CSR, the script is executed, with the certificate name passed as the first (and only) positional argument. The content of the CSR is provided to the scripts STDIN in PEM Encoded format.
  3. The script applies logic based on the provided input, determines if the CSR should be signed, and exits accordingly.
  4. Exit zero - Sign the certificate request.
  5. Exit non-zero - Do not sign the certificate request.

Tying it all together with an autosigning script

Based on the two capabilities above, I've written some simple Ruby to do the following:

  1. Validate the certificate name passed to the script as ARGV0 to ensure it meets the hostname standards.
  2. Query the Azure platform and ensure that the requesting instance exists in our subscription (sourced-dev).
  3. Query the Azure platform and ensure the requesting instance has been tagged correctly for the environment, specifically:
  4. That the service_tier tag exists, and its value matches the regex '^(prod|nonprod|lab)$'
  5. The business_unit tag exists, and its value matches the regex '^a-z0-9{4}$'
  6. The unique_instance_id tag exists, and its value matches the regex '^a-z0-9{3}$''

This is implemented as follows:

  • Create the /opt/autosign/autosign.rb file with the script contents below.
  • Ensure that the permissions are pe-puppet:pe-puppet and mode 750 for PE deployments.

  • Update the puppet.conf file's master section and reference our autosign script master autosign = /opt/autosign/autosign.rb
  • Restart your Puppet server process to ensure that your changes have taken effect

# systemctl restart pe-puppetserver

Testing and results

Now that our policy script is in place, we can launch an instance in Azure and have the instance bootstrap the Puppet agent. Based on where the instance resides and how it is configured, the CSR will be signed, or not signed.

  • Test 1
    • The instance does not exist in our subscription.
    • Do not sign certificate (exit 1).
  • Test 2 The instance resides in our subscription. The instance is missing the mandatory tag missing_tag. * Do not sign certificate (exit 1).
  • Test 3 The instance resides in our subscription. The instance has all the required tags. The instance tag values don't match the defined regex. Do not sign certificate (exit 1).
  • Test 4 The instance meets all our requirements! Sign the CSR (exit 0)

Summary

As you can see from the above, using policy-based autosigning provides a fast, flexible and secure way to automatically sign your Puppet certificate requests for instances that reside on the Microsoft Azure platform, or any platform that can programmatically deploy an instance.

Keiran Sweet is a consultant with Sourced Group and is currently based in Toronto, Canada. He works with customers to automate more and integrate with next-generation technologies. He has been using Puppet for seven years and presented a talk, Order in a World of Snowflakes, at PuppetConf 2015.

Learn more

Puppet sites use proprietary and third-party cookies. By using our sites, you agree to our cookie policy.