Automating Puppet Certificates Renewal
Raphaël Pinson
2/18www.camptocamp.com /
Who am I?
■ Raphaël Pinson (@raphink)
○ Infrastructure Developer & Training Leader
○ Augeas & Augeasproviders developer
○ Various contributions to Puppet & Ecosystem
3/18www.camptocamp.com /
Camptocamp
■ Switzerland / France / Germany
■ Open-source development/integration expert
■ ~ 90 employees
■ Puppet user and contributor since 2008
■ Main contributor to the Puppet Forge
4/18www.camptocamp.com /
CA cert has expired
5/18www.camptocamp.com /
CA renewal options
NEWCAKEY
PAIR
NEWCACERT
FROMKEYPAIR
AUTOMATECA
CERTDEPLOYMENT
AUTOMATEAGENT
CERTDEPLOYMENT
6/18www.camptocamp.com /
7/18www.camptocamp.com /
puppetlabs/certgen
■ Install from Puppet Forge
mod 'puppetlabs-certregen', '0.2.0'
■ Regenerate CA cert
$ sudo puppet certregen ca ca_serial 01–ca_serial 01
■ Deploy new CA cert (before it expires!)
include certregen::client
8/18www.camptocamp.com /
How about agent certificates?
9/18www.camptocamp.com /
Certificate autosign
■ autosign.conf
○ Insecure by design
○ Don't use
■ Autosign policy
○ (possibly) secure autosigning
○ Use psk, unique tokens, etc.
○ See also danieldreier/puppet-autosign
10/18www.camptocamp.com /
The puppet_certificate type
■ Automate Puppet
certificate generation
■ Manage with Puppet
manifests
11/18www.camptocamp.com /
Cleaning certificats on CA
■ Required before new certificate
can be generated
■ Requires to tune the CA API in auth.conf
{
name: "Allow nodes to delete their own certificates",
match-request: {
path: "^/puppet-ca/v1/certificate(_status|_request)?/([^/]+)$"
type: regex
method: [delete]
},
Allow: "$2",
sort-order: 500
}
12/18www.camptocamp.com /
Unique renewal tokens
■ Use hashed token incl. unchangeable trusted facts
■ Sample hashing function (compatible with Terraform's
base64sha256 builtin function)
■ Generate unique token per node in Puppet manifest:
Puppet::Parser::Functions.newfunction(:base64_sha256, :arity => 1, :type => :rvalue) do |args|
Digest::SHA256.base64digest(args[0])
end
# $psk is a secret parameter (e.g. from hiera)
# $certname comes from trusted facts
$token = base64_sha256("${psk}/${certname}")
13/18www.camptocamp.com /
Adapt autosign script
#!/usr/bin/env ruby
require 'openssl'
request = STDIN.read
csr = OpenSSL::X509::Request.new(request)
# Don't you love OpenSSL's nested values?
challenge = csr.attributes.select { |a| a.oid == "challengePassword" }.first.value.value.first.value
exit 3 if challenge.nil?
certname = ARGV[0]
hash = Digest::SHA256.base64digest("#{autosign_psk}/#{certname}")
if challenge == hash
exit 0
end
exit 1
14/18www.camptocamp.com /
Throw in certificate extensions
def get_ext(csr, name)
Puppet::SSL::Oids.register_puppet_oids
# Some more OpenSSL nested values
exts = csr.attributes.select{ |a| a.oid == "extReq" }[0].value.value[0].value
# Turtles all the way down
val = exts.select { |e| e.value[0].short_name == name }[0].value[1].value
OpenSSL::ASN1.decode(val).value
end
pp_role = get_ext(csr, 'pp_role')
pp_environment = get_ext(csr, 'pp_environment')
hash = Digest::SHA256.base64digest("#{autosign_psk}/#{certname}/#{pp_role}/#{pp_environment}")
■ Lock token to specific trusted facts
15/18www.camptocamp.com /
Couple with trusted facts provisioning
$role = $::trusted['extensions']['pp_role']
include sprintf(
'::roles_c2c::%s', regsubst($role, '/', '::', 'G')
)
■ Dynamic provisioning (no server code added)
■ Safe because linked to certificate
16/18www.camptocamp.com /
Put it all together!
# csr_attributes.yaml
---
custom_attributes:
1.2.840.113549.1.9.7: '$
{token}'
# in common Puppet profile
puppet_certificate { $certname:
ensure => valid,
waitforcert => 60,
renewal_grace_period => 20,
clean => true,
}
17/18www.camptocamp.com /
Automating Puppet Certificates Renewal

Automating Puppet Certificates Renewal

  • 1.
    Automating Puppet CertificatesRenewal Raphaël Pinson
  • 2.
    2/18www.camptocamp.com / Who amI? ■ Raphaël Pinson (@raphink) ○ Infrastructure Developer & Training Leader ○ Augeas & Augeasproviders developer ○ Various contributions to Puppet & Ecosystem
  • 3.
    3/18www.camptocamp.com / Camptocamp ■ Switzerland/ France / Germany ■ Open-source development/integration expert ■ ~ 90 employees ■ Puppet user and contributor since 2008 ■ Main contributor to the Puppet Forge
  • 4.
  • 5.
    5/18www.camptocamp.com / CA renewaloptions NEWCAKEY PAIR NEWCACERT FROMKEYPAIR AUTOMATECA CERTDEPLOYMENT AUTOMATEAGENT CERTDEPLOYMENT
  • 6.
  • 7.
    7/18www.camptocamp.com / puppetlabs/certgen ■ Installfrom Puppet Forge mod 'puppetlabs-certregen', '0.2.0' ■ Regenerate CA cert $ sudo puppet certregen ca ca_serial 01–ca_serial 01 ■ Deploy new CA cert (before it expires!) include certregen::client
  • 8.
  • 9.
    9/18www.camptocamp.com / Certificate autosign ■autosign.conf ○ Insecure by design ○ Don't use ■ Autosign policy ○ (possibly) secure autosigning ○ Use psk, unique tokens, etc. ○ See also danieldreier/puppet-autosign
  • 10.
    10/18www.camptocamp.com / The puppet_certificatetype ■ Automate Puppet certificate generation ■ Manage with Puppet manifests
  • 11.
    11/18www.camptocamp.com / Cleaning certificatson CA ■ Required before new certificate can be generated ■ Requires to tune the CA API in auth.conf { name: "Allow nodes to delete their own certificates", match-request: { path: "^/puppet-ca/v1/certificate(_status|_request)?/([^/]+)$" type: regex method: [delete] }, Allow: "$2", sort-order: 500 }
  • 12.
    12/18www.camptocamp.com / Unique renewaltokens ■ Use hashed token incl. unchangeable trusted facts ■ Sample hashing function (compatible with Terraform's base64sha256 builtin function) ■ Generate unique token per node in Puppet manifest: Puppet::Parser::Functions.newfunction(:base64_sha256, :arity => 1, :type => :rvalue) do |args| Digest::SHA256.base64digest(args[0]) end # $psk is a secret parameter (e.g. from hiera) # $certname comes from trusted facts $token = base64_sha256("${psk}/${certname}")
  • 13.
    13/18www.camptocamp.com / Adapt autosignscript #!/usr/bin/env ruby require 'openssl' request = STDIN.read csr = OpenSSL::X509::Request.new(request) # Don't you love OpenSSL's nested values? challenge = csr.attributes.select { |a| a.oid == "challengePassword" }.first.value.value.first.value exit 3 if challenge.nil? certname = ARGV[0] hash = Digest::SHA256.base64digest("#{autosign_psk}/#{certname}") if challenge == hash exit 0 end exit 1
  • 14.
    14/18www.camptocamp.com / Throw incertificate extensions def get_ext(csr, name) Puppet::SSL::Oids.register_puppet_oids # Some more OpenSSL nested values exts = csr.attributes.select{ |a| a.oid == "extReq" }[0].value.value[0].value # Turtles all the way down val = exts.select { |e| e.value[0].short_name == name }[0].value[1].value OpenSSL::ASN1.decode(val).value end pp_role = get_ext(csr, 'pp_role') pp_environment = get_ext(csr, 'pp_environment') hash = Digest::SHA256.base64digest("#{autosign_psk}/#{certname}/#{pp_role}/#{pp_environment}") ■ Lock token to specific trusted facts
  • 15.
    15/18www.camptocamp.com / Couple withtrusted facts provisioning $role = $::trusted['extensions']['pp_role'] include sprintf( '::roles_c2c::%s', regsubst($role, '/', '::', 'G') ) ■ Dynamic provisioning (no server code added) ■ Safe because linked to certificate
  • 16.
    16/18www.camptocamp.com / Put itall together! # csr_attributes.yaml --- custom_attributes: 1.2.840.113549.1.9.7: '$ {token}' # in common Puppet profile puppet_certificate { $certname: ensure => valid, waitforcert => 60, renewal_grace_period => 20, clean => true, }
  • 17.