Running Munki with Chef SSL Client Certificates in Docker

Previously, I wrote about building a Docker container for Munki with Chef installed. Having built that container, it’s now time to put it to use.

Assuming you’ve got a working Chef server set up, we can run our Munki-with-Chef container and register it.

Preparing the Server:

First, we need to set up the CA we’re going to use with Munki, via Chef. This will also assume you have a chef repo in ~/chef-repo/ that is set up according to the Chef documentation.

I’ve described the general process in a previous blog post here, but I’ve changed enough of it that I’m going to repeat a lot of it here.

  1. On the server/workstation, download the cookbook:
    knife cookbook site install x509
  2. Now delete it and clone my version:
    git clone --branch development https://github.com/nmcspadden/chef-cookbook-ssl.git x509
  3. Clone the MunkiSSL cookbook from Github:
    git clone https://github.com/nmcspadden/chef-cookbook-munkiSSL munkiSSL
  4. Upload all the cookbooks to the server:
    knife cookbook upload -a
  5. Create the ‘certificates’ data bag:
    knife data bag create certificates
  6. Create the CA (I’m storing it in my home directory for this example):
    chef-ssl makeca --dn '/CN=ChefCA' --ca-path /home/nmcspadden/chefCA
    Pick any passphrase you want.

Running the Container:

In this blog post, I’m going to call this container “munki2” so as not to interfere with my existing Munki container.

Prepare a data-only container to keep our data in:
docker run -d --name munki2-data --entrypoint /bin/echo nmcspadden/munki-chef Data-only container for munki2

Run the container:
docker run -d --name munki2 --volumes-from munki2-data -p 443:443 -h munki2.domain.com nmcspadden/munki-chef

This run command sets the open port to 443, for SSL connections, and uses the munki2-data data container to access the repo. In addition, I’ve set the hostname here manually as well, using the -h option.

If you are just testing this out and don’t have your Chef server entered into your DNS, you can fix that inside the container using the --add-host option like so:
docker run -d --name munki2 --volumes-from munki2-data -p 443:443 -h munki2.domain.com --add-host chef.domain.com:10.0.0.1 nmcspadden/munki-chef

The first step after running the container is to check in with Chef:
docker exec munki2 /usr/bin/chef-client --force-logger --runlist "recipe[x509::munki2_server]"

Here, we’re using the [x509::munki2_server] recipe to generate a private key and send a CSR to the Chef CA.

On the Chef server or workstation, you’ll need to sign the CSR:
chef-ssl autosign --ca-name="ChefCA" --ca-path=/home/nmcspadden/chefCA

Back on the Docker host, run the x509::munki2_server recipe again to receive the signed certificate:
docker exec munki2 /usr/bin/chef-client --force-logger --runlist "recipe[x509::munki2_server]"

Screenshot 2015-03-03 14.41.25

You can verify the certificate’s existence on the Chef server / workstation:
knife search certificates "host:munki2.domain.com" -a dn

Now that the certificates are present, it’s time to add in the new Nginx config file to tell the webserver to use client certificates:
cat munki-repo-ssl.conf | docker exec -i munki2 sh -c 'cat > /etc/nginx/sites-enabled/munki-repo.conf'

The Nginx configuration looks like this:


server {
listen 443;
ssl on;
ssl_certificate /etc/ssl/munki2.domain.com.crt;
ssl_certificate_key /etc/ssl/munki2.domain.com.key;
ssl_client_certificate /etc/ssl/munki2_ca.crt;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
ssl_verify_client on;
server_name munki;
location /repo/ {
alias /munki_repo/;
autoindex off;
}
}

The three important file paths that must be correct are ssl_certificate, ssl_certificate_key, and ssl_client_certificate. If any of these paths are wrong or can’t be found, Nginx will not start and your Docker container will immediately halt.

For reference, the ssl_protocols and ssl_ciphers are configured for perfect forward secrecy.

Otherwise, the configuration for Nginx for the Munki repo remains the same as the non-SSL version – we’re serving the file path /munki_repo as https://munki2/repo/.

Now restart the container to reload the Nginx configuration:

docker stop munki2 && docker start munki2

We have a working Munki server on port 443, now it needs to be populated.

Configure the Clients to use Munki with SSL certificates:

If you are testing this out, you probably don’t have munki2 or Chef in your DNS entry. You’ll need to add them to your /etc/hosts file on the clients first:
10.0.0.1 munki2 munki2.domain.com
10.0.0.2 chef chef.domain.com

Detailed instructions on configuring Munki with SSL certificates can be found on the official wiki, but I’m going to recreate the steps here.

  1. On the client, run the [x509::munki2_client] recipe to generate a CSR:
    sudo chef-client --runlist "recipe[x509::munki2_client]"
  2. On the Chef Server or workstation, use chef-ssl to sign the CSR:
    chef-ssl autosign --ca-name="ChefCA" --ca-path=/home/nmcspadden/chefCA
  3. On the OS X client, run the recipe again to receive the signed certificate:
    sudo chef-client --runlist "recipe[x509::munki2_client]"
  4. With the client set up with its certificate, now it’s time to configure Munki. Run the [munkiSSL::munki] recipe:
    sudo chef-client --runlist "recipe[munkiSSL::munki]"
    This recipe copies the client certificates from /etc/ssl/ into /Library/Managed Installs/certs/ where Munki can use them.
  5. Change the ManagedInstalls.plist defaults:
    1. sudo defaults write /Library/Preferences/ManagedInstalls SoftwareRepoURL "https://munki2/repo"
    2. sudo defaults write /Library/Preferences/ManagedInstalls SoftwareRepoCACertificate "/Library/Managed Installs/certs/ca.pem"
    3. sudo defaults write /Library/Preferences/ManagedInstalls ClientCertificatePath "/Library/Managed Installs/certs/clientcert.pem"
    4. sudo defaults write /Library/Preferences/ManagedInstalls ClientKeyPath "/Library/Managed Installs/certs/clientkey.pem"
    5. sudo defaults write /Library/Preferences/ManagedInstalls UseClientCertificate -bool TRUE
  6. Finally, test out the client:
    sudo /usr/local/munki/managedsoftwareupdate -vvv --checkonly

Leave a comment