PKI with Puppet

When managing computers, cryptography should be used at least to ensure strong authentication and confidentiality. However, it seems that many admins choose to ignore it, and only use SSH in the most basic way. This is probably due to the perceived hard work of rolling out your own certification authority, managing it, and generating tons of certificates.

This is where an automated management system should come to the rescue. I will describe a simple approach with Puppet, related to secure monitoring.

The simplest approach is to use Puppet built-in PKI. Certificates are generated for each clients, that you sign with the puppetca command. This approach has two drawbacks : a compromised service that uses a puppet certificate can now query the puppetmaster (this is probably not a huge problem, as the compromise of a random server should not result in the compromise of the whole infrastructure), and, more importantly, it becomes harder to use for hosts not managed by puppet and user authentication.

Another way is to run a CA, or an intermediate CA, with Puppet. In my case, I will describe a very basic network with a single puppetmaster. The compromise of that master leads to the compromise of the whole network. More secure environment should be handled in a different way, but you cannot use a single puppetmaster in such environments anyway. In that case, it makes sense to store secrets on the puppetmaster.

First of all, distributing secrets through puppet can be done either through the catalog (with the content file attribute for example) or a specific file mapping, such as that :

[private]
    path /etc/puppet/private/%d/%h/
    allow *.my.internal.domain

So if coincoin.my.internal.domain accesses some file through puppet:///private/xxx, it will map to /etc/puppet/private/my.internal.domain/coincoin/xxx. In my case, I will distribute secret keys and certificates through this mapping. Secret keys and CSRs will be generated on the puppetmaster, and certificates will be signed on an external host. It all works around a define like that :

define newcsr($type,$keyowner='root',$keygroup='root'
     ,$subject="/OU=Organization Unit/O=Organization name/CN=$fqdn")
{
    # this is the exported resource that creates the CSR
        @@exec {
                "gencsr-$hostname-$type":
                        command => "openssl req -new -nodes -newkey rsa:2048
                            -keyout /etc/puppet/private/it.int/$hostname/key/$type.key
                            -out /etc/puppet/private/it.int/$hostname/csr/$type.csr
                            -subj '$subject' -batch",
                        creates => "/etc/puppet/private/it.int/$hostname/csr/$type.csr",
                        path => '/bin:/usr/bin',
                        require => File["/etc/puppet/private/it.int/$hostname/csr",
                             "/etc/puppet/private/it.int/$hostname/key"],
                        logoutput => on_failure,
                        tag => 'certificate-signing-requests';
        }

        # This only manages the file access rights on the keys file, not its content
        @@file {
                "/etc/puppet/private/it.int/$hostname/key/$type.key":
                     ensure => file, mode => 440,
                     owner => 'puppet',
                     group => 'puppet',
                     require => Exec["gencsr-$hostname-$type"],
                     tag => 'certificate-signing-requests';
        }

    # This retrieves the key and certificate
        file {
                "/etc/ssl/private/$type.key":
                        source => ["puppet:///private/key/$type.key","puppet:///files/empty"],
                        owner => $keyowner,
                        group => $keygroup,
                        mode => 640;
                "/etc/ssl/certs/$type.pem":
                        source => ["puppet:///private/cert/$type.pem","puppet:///files/empty"],
                        owner => 'root',
                        group => 'root',
                        notify => Exec['c_rehash'],
                        mode => 644;
        }
}

You will obviously need to collect those execs and files on your puppetmaster :

        File<<| tag == 'certificate-signing-requests' |>>
        Exec<<| tag == 'certificate-signing-requests' |>>

Just insert something like that, for example in your syslog class :

newcsr { "syslog": type => 'syslog', subject=>"/OU=SYSLOG/O=My company/CN=$fqdn", keygroup=>'syslog'; }

You will end up with a CSR in the proper directory. I then use a script that imports all those CSRs in my PKI system with rsync, I sign them, and the same script puts the certificate in the proper place.

This approach is debatable, because :

  • As I use a script anyway, I could make it smarter and generate the keys on the puppet node, then use my script to retrieve CSRs on all nodes. This would be a bit more work, but would arguably be better. I didn’t care doing this, because if you own the puppetmaster, you can retrieve all secrets anyway. There is no security gain in doing this, and storing the secrets on the puppetmaster allows me to reinstall nodes with all their certificates and secrets easily.
  • On the other hand, I could have installed my CA on the puppetmaster and use it to automatically sign all those certificates. This is a viable option, but there is an obvious trade-off here. It should be noted it takes care of CRL generation and dissemination.
  • Collecting exported execs and files on the puppetmaster is incredibly dangerous (as described here). You have to be very cautious with them. I will describe at a later date the way I manage this, but it should be noted that only the $fqdn is derived from a fact, and thus is the only input that should be validated. Yes it would have been nice if the the $fqdn was extracted from the certificate subject.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s