Replacing Foremans web SSL certificate.

Foreman does a great job of providing SSL support out-of-the-box, it does this by using the SSL certificates generated by your puppet-ca. Unless your users web browsers all trust the puppet CA (unlikely), any human user of Foreman is going to get SSL warnings. Replacing Foremans SSL certifcate with one that's signed by a default trusted CA requires some care for 2 reasons:

The following diagram shows the various components talking to the Forman server and which communications require the SSL keys changing.

For the following examples, the new certificates live in the following locations:

Foreman

Foreman is presented via mod_passenger on Apache, so the SSL keys need to be changed in your apache VirtualHost. For me this was /etc/httpd/conf.d/05-foreman-ssl.conf.

The following options need to be changed: SSLCertificateFile, SSLCertificateKeyFile and SSLCertificateChainFile. It is important that you do not change SSLCACertificateFile or SSLCARevocationFile, as these are used for client authentication. Your final config should look like this:

  ## SSL directives
SSLEngine on
SSLCertificateFile      "/etc/pki/tls/certs/puppet.example.com.crt"
SSLCertificateKeyFile   "/etc/pki/tls/private/puppet.example.com.key"
SSLCertificateChainFile "/etc/pki/tls/certs/cachain.crt"
SSLCACertificatePath    "/etc/pki/tls/certs"
SSLCACertificateFile    "/var/lib/puppet/ssl/certs/ca.pem"
SSLCARevocationFile     "/var/lib/puppet/ssl/ca/ca_crl.pem"
SSLVerifyClient         optional
SSLVerifyDepth          3
SSLOptions +StdEnvVars

If you use the Console feasture for foreman, you will also want to change the websocketssslkey and websocketssslcert keys in /etc/foreman/settings.yaml. For example:

:websockets_ssl_key:  /etc/pki/tls/private/puppet.example.com.key
:websockets_ssl_cert: /etc/pki/tls/certs/puppet.example.com.crt

Smart Proxy

If you have your smart proxies running on the same box as foreman, make sure foreman_ssl_ca is not defined in /etc/foreman-proxy/settings.yaml and it will read the CA from the main foreman settings.

Puppet

Change sslca in /etc/puppet/foreman.yaml, do not change sslcert or ssl_key. For example:

:ssl_ca: "/etc/pki/tls/certs/cachain.crt"
:ssl_cert: "/var/lib/puppet/ssl/certs/puppet.example.com.pem"
:ssl_key: "/var/lib/puppet/ssl/private_keys/puppet.example.com.pem"

Managing with puppet

If you use manage your foreman and puppet install with the theforeman/puppet and theforeman/foreman modules, you can configure all the above with the following hiera data:

foreman::ssl: true
puppet::server_foreman_ssl_ca: '/etc/pki/tls/certs/cachain.crt'
puppet::server_foreman_url: 'https://puppet.example.com'
foreman::server_ssl_key: '/etc/pki/tls/private/puppet.example.com.key'
foreman::server_ssl_cert: '/etc/pki/tls/certs/puppet.example.com.crt'
foreman::server_ssl_chain: '/etc/pki/tls/certs/cachain.crt'
foreman::servername: 'puppet.example.com'
foreman::foreman_url: 'https://puppet.example.com'
foreman::websockets_ssl_key: '/etc/pki/tls/private/puppet.example.key'
foreman::websockets_ssl_cert: '/etc/pki/tls/certs/puppet.example.crt'

Importing Puppet Classes into Puppet Dashboard

Puppet Dashboad has a concept of classes, which can be really useful if you make use of external nodes and link it to the dashboard. Unfortunately it doesn’t currently have a way to auto-import classes defined in your puppet manifests.

The following is a little bit of python hacked together to provide this functionality. It looks at a directory for a list of modules and the database details for puppet dashboard (only works for MySQL). It will add any modules to puppet dashboard that are not already defined and remove any extras that no longer exist in your modules path.

#!/usr/bin/python
import os
import MySQLdb
import datetime
#Path to Puppet modules directory
modulesdir="/etc/puppet/modules/"
#MySQL details: user = "" password = "" host = "" database = "" availmodules = [] currentmodules = []`{lang="python"}
 
#Get list of avaible modules from filesystem  
for item in os.listdir(modulesdir):  
    if os.path.isdir(os.path.join(modulesdir,item)) and not item.startswith('.'):  
        availmodules.append(item)  
        availmodules = set(availmodules)
 
#Get list of current modules from database  
db = MySQLdb.connect(host=host,user=user,passwd=password,db=database)  
cursor = db.cursor()  
cursor.execute("SELECT name FROM node\_classes")  
for [name] in cursor.fetchall():  
    currentmodules.append(name)  
    currentmodules = set(currentmodules)
 
extramodules = currentmodules - availmodules
 
print "Availble:\\t"+str(availmodules)  
print "Current:\\t"+str(currentmodules)  
print "Extra:\\t\\t"+str(extramodules)
 
#Add or update current availble modules  
currenttime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')  
for module in availmodules:  
    if module in currentmodules:  
        #module already exists, just update timestamp  
        cursor.execute("UPDATE node\_classes SET updated\_at = %s WHERE name = %s",(currenttime,module))  
    else:  
        #module doesn't exist, insert it  
        print "Adding: "+str(module)  
        cursor.execute("INSERT INTO node\_classes
        (name,created\_at,updated\_at) vALUES
        (%s,%s,%s)",(module,currenttime,currenttime))  
        #Delete any extra modules  
        for module in extramodules:  
            print "Deleting:"+str(module)  
            cursor.execute("DELETE from node\_classes WHERE name = %s",(module))  
 
db.commit()  
cursor.close()  
db.close()

There are better ways to get a list of classes, for example the interface utils, but this requires puppet 2.6.5 at a minimum and I’m running 2.6.2. It would also be desirable to input data into the dashboard via an API instead of directly into the database, but AFAIK this functionality isn’t available yet.

I personally run the above as part of the post-commit hook on the puppet repository. But you could also run it via cron or manually.