Adobe CC 2015: Another Circle Around The Drain

Well, Adobe has updated the CC products to 2015 versions. That means another day spent dedicated to downloading and building packages via CCP.

In my previous blog post about Adobe CC, I covered how to mass-import them into Munki while still addressing the nasty uninstaller bug.

The Uninstaller Bug

As described in the previous post (linked above), the problem with device-based licensing for Adobe is that the uninstallers are very aggressive. Uninstalling a single device-based package will nuke the licensing for all other Adobe software that is licensed with that same serial number. In other words, if you install Photoshop CC and Dreamweaver CC, and uninstall Dreamweaver CC, Photoshop CC will complain that it is not licensed and needs to be fixed (and thus won’t run).

That’s irritating.

To address this, one solution is to use the Serial number installer package with the Named License uninstaller package. The Named License uninstaller will not nuke the entire license database on that machine. This allows us to successfully install and uninstall without affecting other Adobe products on the machine.

Note: There are other issues with this approach if you do not have unlimited licensing agreements (ETLA), please see the previous blog post for details.

The simplest way to handle this is to create two folders – “CC Packages” and “CC NoSerial Packages”. Use CCP to create Serial Number licensing packages in the “CC Packages” folder for all new CC 2015 products. Then create a Named license package for the same product in the “CC NoSerial Packages.”

IMPORTANT NOTE about Munki: The import script will use filenames as item names. You may wish to either create your CCP packages with “2015” as a suffix to differentiate it from the previous versions, or adjust the names in the pkginfo files manually, or adjust your manifests to include the appropriate new item names. Also, you may need to adjust icon names. You probably don’t want to reuse the same item name for CC 2015 products as CC 2014 products, otherwise Munki may try to install Adobe updates imported via aamporter on versions that are too high.

Importing The Packages Into Munki

Now that you have two copies of each product in separate folders, we can combine the right parts to allow easy importing into Munki.

Copy the Uninstaller packages from the “CC NoSerial Packages” folder for each product into the equivalent “CC Packages” product folder.

End result is that the “CC Packages” folder will now contain each of the separate CCP products, each of which will contain a “Build” folder with the Serial Number license installer, and a Named license uninstaller.

Now we can run Tim Sutton’s mass Adobe CC import script. Before executing, however, you may wish to open it up and change Line 42 to “2015”:

MUNKIIMPORT_OPTIONS = [
    "--subdirectory", "apps/Adobe/CC/2014",
    "--developer", "Adobe",
    "--category", "Creativity",
]

becomes

MUNKIIMPORT_OPTIONS = [
    "--subdirectory", "apps/Adobe/CC/2015",
    "--developer", "Adobe",
    "--category", "Creativity",
]

Now you can run the script on your “CC Packages” folder:
./munkiimport_cc_installers.py CC\ Packages

The script will create the appropriate install and uninstall DMGs, and pkginfos for all of these products. Don’t forget to run makecatalogs afterwards.

In my initial testing, none of the CC 2015 apps produced any errors or problems installing or uninstalling via Munki.

We Are Imagr (And So Can You)

For a long time, Mac Admins have been relying on the wonderfully useful tool DeployStudio. This free tool has been the mainstay of many deployment strategies, and has been my old faithful for years.

Despite its utility and functionality, it has some issues. Some of its functionality is almost black-box opaque, and we have to make some guesses and assumptions, and do some wrestling to make it work the way we want. The biggest issue, of course, is that it’s OS X-only, meaning that Mac deployment relies on having a Mac in a data-center environment. This makes many network administrators unhappy, as Apple does not offer an enterprise-level Mac anymore, not since the 2009 XServe was discontinued. Putting Mac Minis in the data center has raised eyebrows among the server purists and network neckbeards, but no one could deny how useful DeployStudio is. But since it’s not open-source, there’s not much the Mac admin community can really do about it.

Finally, that’s about to change – thanks to Graham Gilbert and his incredible work on his tool called Imagr. Imagr is an open-source deployment tool that runs in a NetInstall environment and makes running scripts, restoring images, and installing packages easy.

Imagr now leverages the functionality of Pepijn Bruienne’s AutoNBI to make the creation of all-inclusive NetBoot Images (hereafter referred to as “NBIs”) easy and straightforward.

Imagr is in its early stages of development, so there’s obviously lots of rough edges and work to be done. Improvements are being made constantly, and by many contributors, so you can get an idea of the excitement of the community around an open-source tool to do deployments with.

Despite Graham’s excellent work at the wiki, getting a chance to play with it may be a bit foreboding to admins who aren’t sure where to start.

First, I’d suggest reading Greg Neagle’s excellent starting point blog posts: Part 1 and Part 2.

I’d like to get into a bit more detail so that future admins who want to test can investigate this thoroughly.

The Setup

You need four things to test out Imagr:

  1. A NetBoot server.
    The easiest way to accomplish this is with an OS X Server running the NetBoot service. If you have an existing DeployStudio server running on OS X Server, that would be a perfect choice. See Greg Neagle’s post above for how you can use Imagr with existing DeployStudio NBIs. I’ll be referring to this as the “Netboot server.”
  2. A target device or VM to test with.
    I highly recommend using VMWare Fusion’s NetBoot compatibility to make testing faster, but if you’ve got a physical machine you’re willing to blow away, that works just fine too. I’ll be referring to this as the “client.”
  3. A web server.
    OS X Server running a Web service will work just fine. I’m using my existing Munki repo. I’ll refer to this as the “web server.” If you want to use OS X Server 4’s default web server, just place all relevant files in /Library/Server/Web/Data/Sites/Default/.
  4. A machine to generate your NBIs on, preferably running 10.10.3 build 14D136 (to be compatible with all current Apple hardware).
    This could also be the OS X Server if you’re using one, as long as it’s fully updated to the latest version of OS X. I’ll be referring to this as the “admin computer.”

In this post, I’m going to use my existing 10.9.5 OS X Server 3 to serve out NetBoot (since it’s already used for DeployStudio). I’m using my own workstation as the admin computer, running 10.10.3 build 14D136 to generate the NBIs. I’m testing with a VMWare Fusion 7 Pro VM as client that will be NetBooting.

Preparing Your NBI:

Note: pre-step zero: download the latest Yosemite installer from the Mac App Store. I’ll be using the default path: /Applications/Install OS X Yosemite.app in this example.

  1. Create a file called imagr_config.plist that is hosted on your Web server.
    I’m hosting it on my munki repo in a folder called ‘imagr’, so it can accessed at http://munki/repo/imagr/imagr_config.plist. We’ll fill the contents of this file shortly. For now, just make sure it’s accessible.
  2. On the admin computer, download or clone Imagr from the GitHub page:
    git clone https://github.com/grahamgilbert/imagr.
  3. Create a file called config.mk in the same directory as the “Makefile” that is included with the Imagr clone (~/Applications/imagr/config.mk would be the path in my example).
  4. This config.mk file is going to be used to override the variables. Change the URL, which should be the accessible URL of your “imagr_config.plist” file. Additionally, if you have existing NBIs (from DeployStudio, for example), make sure you choose an index that does not collide with an existing one. The default is 5000:
    URL="http://munki/repo/imagr/imagr_config.plist"
    APP="/Applications/Install OS X Yosemite.app"
    OUTPUT=~/Desktop/
    NBI="Imagr-Testing"
    ARGS="-e -p" # Enable Netboot set, include python
    INDEX=5000
    

  5. From inside the imagr directory, run this command:
    make nbi

  6. At the end of the lengthy command run, you should have a file located in your OUTPUT folder (in this example, on the Desktop) called “Imagr-Testing.nbi”.
  7. This NBI needs to be copied to your NetBoot server. If you’re using OS X Server as your NetBoot server, you need to place this file in /Library/NetBoot/NetBootSP0/.
  8. Launch Server.app and go the NetInstall section. You should see it show up:
    Screen Shot 2015-05-12 at 8.55.00 AM
  9. Select your “Imagr-Testing” boot image and click on the gear icon to get its properties and details:
    Screen Shot 2015-05-12 at 8.55.12 AM
  10. First, make sure that the index it chooses (by default, 5000) does not collide with other NBI indexes. If it does, change the index number here (and then change your config.mk to build NBIs using a different index).
    Screen Shot 2015-05-12 at 8.54.48 AM
  11. By default, the NBI only makes itself available to compatible Mac models. This is great for actual deployment, but if you want to do testing with a VM, you might need to turn this off. Change the availability so that it’s open for “all Mac models”:
    Screen Shot 2015-05-12 at 8.55.12 AM
  12. If you’re doing testing with a VMWare Fusion VM, you’ll also need to make sure that the “Imagr-Testing” NBI is the Default netboot image, as Fusion only works with the default. Select the image, click the gear icon, choose “Use as Default Boot Image.”
    Screen Shot 2015-05-12 at 8.55.19 AM
  13. Verify on your clients. You should be able to see the “Imagr-Testing” netboot image in the Startup Disk pane of the System Preferences.

Creating a Workflow:

Now that the NBI is ready, we need an actual Imagr deployment configuration so that it can be tested. This workflow information is stored in the imagr-config.plist file we created on the Web server earlier. The workflow structure is documented on the wiki.

You can use any text editor to create this plist. For visual completeness, I’ll also include screenshots of Xcode’s visual plist editor.

Here’s the starting empty plist:

First, we need a password. On any computer with Python (such as the Admin computer):
python -c 'import hashlib; print hashlib.sha512("YOURPASSWORDHERE").hexdigest()'
Copy the long hash string and set that as the value of the Password key at the root of the plist:

Screenshot 2015-05-12 09.09.44

Now it’s time to fill out a workflow. This basic workflow is going to install a live package (i.e. not at first boot). This package is a CreateOSXInstallPkg package for 10.10.3 build 14D136. It needs to be accessible via HTTP somewhere, so it’s a prime candidate for going into the “imagr” directory on the Web server I already created.

The package installation can be described like this:

Screenshot 2015-05-12 09.24.06

In this workflow, we’re specifying a restart_action value of restart, which forces Imagr to reboot when the workflow completes (successfully).

The components key lists the actual process for things we want to do. Here, we’re just using a component of type package, which installs a package. We specify the url to the package, and we’re installing this package live, not at first boot, thanks to the value of false for first_boot.

Note that the url key points to a disk image, not a package itself. CreateOSXInstallPkg produces a bundle package, not a flat package, and thus it must be wrapped in a disk image. If you aren’t sure how to do that, follow these steps:

  1. mkdir InstallYosemite-10.10.3
  2. mv InstallYosemite-10.10.3.pkg InstallYosemite-10.10.3/
  3. /usr/bin/hdiutil create -srcfolder InstallYosemite-10.10.3 InstallYosemite-10.10.3.dmg
  4. Copy the resulting disk image to the location where it can be served via Web.

All this workflow accomplishes is installing the CreateOSXInstallPkg package, and then restarts.

Read the documentation to see how you can also leverage Scripts and Image cloning in addition to package installs. The workflow is fairly extensible.

Testing Out Imagr:

We have a NBI image we created, serving out NetBoot from the NetBoot server. We have a workflow in our imagr_config.plist file to install a package. Now it’s time for an actual test.

  1. Boot up a VM or physical device (the client device) to the NetBoot image called “Imagr-Testing” (or whatever you named it).
  2. Log in using the password you specified (in my example, it was “YOURPASSWORDHERE”).
  3. Run your workflow!

Testing it out again, and then again

If you want to make any changes to your NBI (or are testing out new forks/updates to Imagr), you need to rebuild the NBI each time.

  1. make update to rebuild the NBI without needing to build from scratch. This will save time (hat tip to Erik Gomez and Clayton Burlison).
  2. Copy it to your NetBoot server into /Library/NetBoot/NetBootSP0.
  3. Change index number if necessary (which you should do by specifying a unique index number in config.mk.
  4. Change the models to “All Mac models” if booting into a VM.
  5. Set the boot image as default.
  6. Reboot the client machine.
  7. Rinse, wash, repeat!

Fixing Adobe CCP’s Broken Uninstallers

I wrote previously about Adobe Creative Cloud products and Munki.

It’s an ongoing struggle.

This week, I discovered a rather unfriendly issue with the process. If you use Serial Number Licensing (i.e. serialized per-device installs) in Creative Cloud Packager (CCP), you get an Installer.pkg and Uninstaller.pkg that contains all the CC products you checked the boxes for. In most cases, with some exceptions, the Uninstaller packages do the right thing with Munki and uninstall the product.
Screen Shot 2015-04-23 at 9.48.40 AM

However, because these packages are all serialized, if you uninstall any serialized package, it removes the serialization from the device completely. This will break any existing CC apps still remaining on the machine.

More specific example:

  1. Create a Serial Number CCP package for Photoshop CC.
  2. Create a Serial Number CCP package for Dreamweaver CC.
  3. Install them both on the same machine / VM.
  4. Launch both Photoshop CC and Dreamweaver CC.
  5. Use the CCP Uninstaller package for Dreamweaver CC on that machine.
  6. Try to launch Photoshop.

Instead of Photoshop launching as you’d expect, you’re instead greeted by the Adobe Application Manager asking you to sign in and serialize your product – because there are no longer any serialization data on the device.

Uninstalling any CCP-generated product like this will completely remove all serialization.

I spoke to Adobe about this, and this was the response:
Screenshot 2015-04-22 12.20.43

Not great news – this is “semi-expected” behavior, and is potentially a huge problem.

Who Does This Affect?

Anyone who generates CCP packages for individual CC products using Serial Number Licensing can be affected by this. Admins already using Named Licensing will not encounter this issue.

The Solution

Patrick Fergus brought to my attention a clever idea. Since CCP allows the creation of both serialized (Serial Number Licensing) and non-serialized (Named Licensing) packages, we might already have a solution in place. The non-serialized packages don’t uninstall the serialization – because they never install it in the first place.

Thus, it’s possible to combine a serialized installer with a non-serialized uninstaller.

Here’s the general workflow:

  1. Create a Serial Number-licensed CCP package for Photoshop CC.
  2. Create a Serial Number-licensed CCP package for Dreamweaver CC.
  3. Install them both on the same machine / VM.
  4. Launch both Photoshop CC and Dreamweaver CC.
  5. Create a Name-licensed (non-serialized) CCP package for Dreamweaver CC.
  6. Use the Name-licensed (non-serialized) Uninstaller package for Dreamweaver CC to uninstall Dreamweaver on that machine.
  7. Try to launch Photoshop.
  8. It works! Photoshop launches as expected.

Using This Solution With Munki

Incorporating this into Munki is a bit more work.

If you’re starting fresh and haven’t already imported the Adobe CC products yet, you’re in luck, because this is relatively simple. Otherwise, we have to fix the pkginfos for each of the products in the repo.

Haven’t Yet Imported Adobe CC Products Into Munki:

The first step is to read the Munki wiki page about Adobe CC products.

Before you run Tim Sutton’s munkiimport script for CC installers, there’s some setup to be done.

You’ll need to run CCP twice for each package – once to create the Serial Number-licensed installer, and once to create the Name-license installer.

Copy/overwrite the Uninstaller packages from the Name-license versions into the Build folders for each Serial Number-licensed CCP package you created. The end goal here is that each product should be using the Serial Number Installer package and Named Uninstaller package.

Now go ahead and run Tim Sutton’s munkiimport script, and it will do the right thing.

Test thoroughly!

Fixing existing Adobe CC products in a Munki repo:

If you’ve already used Tim Sutton’s munkiimport script for CC installers to import your existing CC packages, it’s now time to fix the broken uninstallers.

You’ll need to run the CCP packages again for each product you want to fix – this time using Named licensing instead of Serial Number licensing, to create non-serialized packages. You can safely delete the Installer.pkg files to save space, as you don’t need them – you only need to keep the ~4mb Uninstaller.pkg files.

Next, you need to wrap each of the Uninstaller.pkg files in a DMG to replace the existing uninstaller DMGs in your Munki repo. You can do this using the same method munkiimport does:
hdiutil create -srcFolder /path/to/Named/Build /path/to/nonserialized/uninstaller.dmg

If I’m creating an uninstaller DMG for Adobe After Effects CC, for example:
hdiutil create -srcFolder Adobe/CC_Packages-NoSerial/AfterEffectsCCNoSerial/Build AfterEffectsCC_Uninstall-13.0.0.dmg
It’s important to make sure that the name of the DMG you are creating is identical to the one you are replacing.

The pkginfo files also need to be fixed for each product. Since the uninstaller items are being replaced, the hash sums for these DMGs must also be replaced with the new one – or Munki will complain that the hashes don’t much and won’t uninstall.

To calculate the SHA256 sum of the DMG, use this command:
shasum -a 256 /path/to/uninstaller.dmg
Then copy the resulting hash (the long string of letters and numbers) into the value of the uninstaller_item_hash key for each pkginfo you are replacing the uninstaller DMG for.

Copy the uninstaller DMGs to the Munki repo in exactly the same place as the previous ones, overwriting the previous DMGs.

Finally, run makecatalogs.

Test thoroughly!

Obvious Downsides

There’s one major issue here – uninstalling an Adobe CC product that was installed with serialization in this manner will not remove the serialization for that product on the device. In other words, if you are trying to count the number of licenses for Photoshop you have, uninstalling Photoshop CC via the Named-license uninstaller will not give you your license back (in the eyes of Adobe).

More than that, even if you uninstall all Adobe CC products from a machine using these non-serialized uninstallers, it won’t actually remove the serialization at the end. According to Adobe licensing, the device will still be using up a seat at the table.

With an ETLA agreement, where we have unlimited licenses and pay annual cost based on the number of Full-Time Employees, this isn’t an issue in our environment. But for anyone who has limited licenses of any of the products, this is an issue that has to be accounted for and worked around.

Possible solutions

If the goal is to remove all serialization from a machine after removing all Adobe CC products, you can use CCP to create a “License File package” – which isn’t actually a package, but a collection of files that includes a binary to serialize, and one to remove all serialization. This “RemoveVolumeSerial” binary (which is not an editable script!) could be run on the machines to remove all serialization.

If you need to remove a specific product license but leave the others untouched, you may have to look into the Adobe Provisioning Toolkit to accomplish what you need.

Signing .mobileconfig Profiles With Keychain Certificates

Generating .mobileconfig profiles should be a straightforward process for the Mac admin. There are many tools to do so – Profile Manager, Apple Configurator (although it lacks the OS-X specific keys), just about every MDM, mcxToProfile, etc.

In most cases, the profiles generated this way are unsigned – meaning that there’s no verification that the profile that gets installed on a client node was the one you wrote. Generally speaking, this isn’t really an issue if you trust your deployment system. I’ve yet to see a case where anyone tried to “exploit” a profile maliciously, but if nothing else, it could be irritating to have someone hijack a profile. It’s a good security practice to ensure that your clients only get installed what you expect, and signing a profile prevents tampering.

Signing a profile is surprisingly easy, thanks to Greg Neagle who pointed me in the right direction.

First, you need to decide what certificate you want to sign your profiles with. This can be any certificate, really, but the value of signing your profile is to use a certificate that your clients automatically trust. You can either use your own institution’s trusted CA (if you have one), or you can use a certificate trusted by a root CA already in the system – such as anything by RapidSSL, StartSSL, VeriSign, etc. It must be a certificate for which you have the private key.

You can also use Apple Developer certificates, if you subscribe to the Apple developer programs (and you should, as a Mac or iOS admin – $99 goes a really, really long way).

In this example, I’m going to use my Apple Developer cert. Sign into the dev center first, and then click on “Certificates”:

Screenshot 2015-04-21 15.32.46

If you don’t already have a certificate, you can add one:
Screenshot 2015-04-21 15.32.59

Once you’ve got the certificate added and downloaded, make sure you import it and the private key into your Keychain. In this example, I’m going to use my organization’s “Developer ID Installer” certificate to sign my certificates.

How do I know which certificates I can use?

Not every certificate in your Keychain will be valid for signing. You can only sign with valid identities. To figure out which of your identities will work for signing things, you can use the security command:

/usr/bin/security find-identity -p codesigning -v

The -p codesigning argument tells it to only display identities that match the codesigning policy.

The -v argument tells security to only display valid identities. (Without this option, you could see revoked or expired identities).

Take note of the name of the certificates that this command outputs – you’ll use one of them in the next section.

Signing an individual profile:

Use the security command to sign the profile using your identity:

/usr/bin/security cms -S -N "Mac Developer Application" -i /path/to/your.mobileconfig -o /path/to/your/signed/output.mobileconfig

As the manpage suggests, the -S argument tells security cms to sign something.

The -N argument, in this case “Mac Developer Application”, tells it to sign something using a certificate that matches the name. Note: it will fail if it can’t figure out which certificate you mean. If there’s any ambiguity (i.e. more than one possible certificate that matches the string you provided), it will fail and complain. Try and be as explicit as possible, and make sure you only use a valid identity found from the previous section.

The -i argument provides an input .mobileconfig file to be signed.

The -o argument provides an output signed .mobileconfig file to be created.

NOTE: When you run this command, you’ll get a security prompt. The security command will request that access to use the keychain, each time it runs (unless you click “Always Allow”). You can control this in the Keychain Access.app by going to “Get Info” on the private key of the certificate and choosing “Access control,” and making changes there.

Signing multiple profiles:

You can use a simple bash script:

In a directory called “Profiles” that is, unsurprisingly, full of .mobileconfig files, go through each one, run the security cms command on it, and create a file with “Signed” in the name.

The s and outName variables are just fun, unreadable ways of using Bash string manipulations to insert “Signed” into the filename.

The end result is that you’ll now have a folder full of ProfileNameSigned.mobileconfig files, signed with the certificate you specified using -N.

NOTE: When you run this script, you’ll get a security prompt. The security command will request that access to use the keychain, each time it runs (unless you click “Always Allow”). You can control this in the Keychain Access.app by going to “Get Info” on the private key of the certificate and choosing “Access control,” and making changes there.

Testing the profile out:

Before deploying, try testing this profile out on a VM. When I install one of these signed VMs, this is what I see in the Profiles pane:
2

If you click on the “Verified” in green, you’ll see the certificate:
Screen Shot 2015-04-22 at 7.27.12 AM

When your profile is signed and trusted, you can rest easy knowing that nobody can mess with it anywhere in transit without breaking the code signing.

Wrestling Adobe CC into Munki

Adobe Creative Cloud is one of those things that admins just can’t escape. Sooner or later some creative or smart person at any given organization is going to stop and think, “Wow, I could really go for some Photoshop right about now,” and then there’s budget THIS and committee THAT and one way or another, you, the admin, end up with 20 GB of Adobe products sitting in your lap and a request to give everyone exactly what they want.

Then of course you discover that Adobe isn’t very good at packaging, and that they expect you actually do all the work yourself. Of course they’ll provide you the basic tool – Creative Cloud Packager – to download and create these packages for you. But it’s still on you to get those all ready.

That’s kind of annoying.

I recently went through this process and boy do I have annoyance enough to share with the whole class. Since I suffered through this, I wish to hopefully make it easier for future generations to deploy Adobe CC using Munki without having to reinvent the wheel completely.

First and foremost, read this page I wrote on the Munki wiki. It describes the process of importing CCP packages into Munki, along with importing updates using Timothy Sutton’s aamporter.

Missing from this wiki page, however, are two things that may be of use to Munki admins: icons, and descriptions.

Icons

Getting icons for 25 different Adobe applications is a royal pain. Independently opening up each app bundle and searching through Contents/Resources/ for the right .icns file is not fun, because, well, there are a lot of them.

I got tired of doing that after the first one, so I tried to figure out a way I could speed up the process.

I simplified the extraction process using an ugly find:
find /Applications/Adobe\ Dreamweaver\ CC\ 2014.1/*.app/Contents/Resources -name "*.icns" -execdir sips -s format png '{}' --out ~/Desktop/$(basename '{}').png \;

That copies all of the .icns files out from inside the Dreamweaver app bundle onto my Desktop, converting them to png format using sips. I still needed to manually sort through all the icons to figure out which one corresponded to the .app bundle’s actual icon.

Being Adobe, they’re not all named consistently, so I can’t just look for the same filename in each application. Some of them are named the same (commonly “appIcon.icns”), so I also can’t extract each of the different applications’ icons into the same folder, because then I’d overwrite some of them.

I realized, ultimately, there was no pretty way to do this.

Instead, I dutifully recorded all the icon names for each Adobe CC application, and wrote a script that would use sips to copy them out into PNG format to a folder of my choice (such as the icons directory of my Munki repo).

That project can be found in my Github repo here.

The script follows as well, for convenience:

The script will check for the existence of each of the Adobe CC products that can be packaged with Creative Cloud Packager (as of writing time), and then pull out the icon if it’s present.

That made it a bit easier for me to give all of my separate Adobe CC apps in Munki nice shiny icons.

The two exceptions are Adobe Exchange Panel CS6, and Gaming SDK. Neither of them install an app with an icon contained inside as their primary executable, so I had to manually download logos from Adobe’s website.

Descriptions

Sadly, descriptions are a bit more work to come by. The best I’ve found so far is from this page on Adobe’s website. I simply copied and pasted those blurbs into my pkginfos.

Update: Pepijn Bruienne brought to my attention that MacUpdate.com also makes a great source of descriptions, that are generally more verbose than the blurbs on the Adobe site I mentioned above.

Here’s an example from MacUpdate for Adobe Acrobat Pro:

Adobe Acrobat allows users to communicate and collaborate more effectively and securely. Unify a wide range of content in a single organized PDF Portfolio. Collaborate through electronic document reviews. Create and manage dynamic forms. And help protect sensitive information.

Securely Bootstrapping Munki Using Chef

In a previous article, I demonstrated a method of bootstrapping a new OS X client using Puppet’s client SSL certificates to secure Munki.

Continuing the topic of testing out Chef, I wanted to get similar behavior from a Chef setup that I can from a Puppet installation. The primary issue here is that Chef, unlike Puppet, doesn’t use built in client certificates – so we have to make them. I’ve previously written about setting up Chef with SSL client certificates, and setting up a Munki docker container to use Chef certificates.

The goal here is to be able to deploy a new computer with Chef and Munki preinstalled via DeployStudio (which runs over HTTPS), and then bootstrap Munki using SSL client certificates – meaning every part of the network deployment process is over a secure channel.

Strap in, because this one’s going to be complicated.

Process Overview

OS X Setup:

  1. Follow the previously-blogged-about PKI process to get an SSL certificate on the Munki server, and on the OS X client.
  2. Install OS X.
  3. Install OS setup packages (admin account, skip registration, bypass setup assistant).
  4. Add Chef & Munki servers to /etc/hosts if not in DNS.
  5. Add Chef client.
  6. Add Chef setup – client.rb and validation.pem files to /etc/chef/.
  7. Add Munki & Munki configuration profile (using SSL client certificates).
  8. Add Outset.
  9. Add Chef first run script.
  10. Add Chef trigger launchdaemon.

On First Boot:

  1. Set the HostName, LocalHostName, and ComputerName.
  2. Perform the initial Chef-client run using recipe [x509::munki2_client] to generate the CSR.
  3. LaunchDaemon that waits for the existence of /etc/ssl/munki2.sacredsf.org.csr triggers:
    1. It will keep running the [x509::munki2_client] recipe while the CSR exists.
    2. The CSR will only be deleted when the CSR is signed on the Chef server.
    3. When the recipe succeeds and the CSR is removed and the .crt file is created, run the [munkiSSL::munki] recipe to copy the certificates into /Library/Managed Installs/certs/.
    4. Touch the Munki bootstrap file.
  4. With the certificates in place, Munki ManagedInstalls profile installed, and the bootstrap trigger file present, Munki can now successfully bootstrap.

The Detailed Process:

Preparing the Deployment:

For my deployments, I like using Greg Neagle’s CreateOSXInstallPkg (henceforth referred to by acronym “COSXIP”) for generating OS X installer packages. Rather than crafting a specific image to be restored using DeployStudio, a package can be used to both install a new OS as well as upgrade-in-place over an existing OS.

One of the perks of using COSXIP is being able to load up additional packages that are installed at the same time as the OS, in the OS X Installer environment.

As mentioned above, we’re going to use a number of specific packages. Here’s what the COSXIP plist looks like:

Note that I’ve added “Dist” to the names of them. Due to a Yosemite requirement that all included packages be distribution packages, I have forcefully converted each package to a distribution using productbuild as described in the above link, and added “Dist” to the end to distinguish them.

The Packages:

AddChefToHosts and AddMunkiToHosts are payload-free packages that just add the IP addresses for my Chef and Munki2 server to /etc/hosts, since this is just in testing and those services don’t yet exist in DNS. The scripts look like this:

#!/bin/sh
echo "10.0.0.1 chef.sacredsf.org" >> "$3/private/etc/hosts"

chef-12.1.0-1.Dist is a specially repackaged-version of the Chef client. You can find the recipe for this in my AutoPkg repo.

The reason I did this is because the Chef-client’s postinstall script assumes that the target volume is a live booted OS X install – which is not true of the OS X install environment. The OS X install environment doesn’t have all OS X features, and the Chef client postinstall script will fail to do certain things like run basename and uname, and the symlinks will not work properly as they are executed in the OS install environment. My AutoPkg recipe addresses these issues and repackages the Chef client in a manner that is more compatible with the OS X install environment.

ChefSetup installs the client.rb and validation.pem files into /etc/chef/. The client.rb file looks like this:

The validation.pem file is the private key of the organization. See this blog post for details.

ClearRegistration, CreateAdmin, Profile-SetupAssistant are packages that bypass the OS X first-time boot setup process, by skipping the device registration, creating a local Admin account, and then skipping the iCloud Setup Assistant on first login. This allows me to boot straight to the Login Window and then login straight to the Desktop with no interruption.

ManagedInstalls-10.10-SSL installs the .mobileconfig profile that configures Munki. It enforces the settings that were accomplished using defaults in a previous blog post.

munkitools-2.2.0-2399 should be obvious.

Outset is the distribution package of Joseph Chilcote’s Outset, a fantastic tool for easily running scripts and packages at boot time and login time (which is easier than writing a new launch agent or launch daemon every time).

Outset-ChefClient installs the initial Chef setup script into /usr/local/outset/firstboot-scripts/. This initial Chef setup script looks like this:

The script sets the hostname to the serial number (which I’m just using in my test environment so I can boot multiple VMs without having all of them be named “Mac.local”), and then runs the Chef client to trigger the generation of the CSR.

You can find the project for this in my GitHub repo.

XCodeCLITools installs the Xcode Command Line tools from the Developer site. This isn’t strictly necessary, but if you run Chef-client manually it will prompt you to install them, so preinstalling saves me some testing time.

ChefCSRTrigger installs the Launch Daemon that watches the path /etc/ssl/munki2.sacredsf.org.csr. So long as that path exists, this Launch Daemon will continue to trigger. The CSR is generated by the first run of the Outset Chef script, and this will keep making a request until the CA signs the CSR. The Launch Daemon looks like this:

It runs this script:

Once the CSR is found, the script will attempt to run the same recipe again. If the recipe succeeds, the CSR will disappear and instead, /etc/ssl/munki2.sacredsf.org.crt will appear. If this file exists after the Chef-client run, the script will proceed to try and run the [munkiSSL::munki] recipe until it has successfully copied over the cert into /Library/Managed Installs/certs/clientcert.pem (which should theoretically only take one run). Then, it will create the Munki bootstrap file.

You can find this project in my GitHub repo.

With all of these packages, you can build your OS X installer to use in DeployStudio:
sudo ./createOSXinstallPkg --plist=InstallYosemite-ChefMunki.plist

Deployment:

When a computer is NetBooted into DeployStudio, and the OS is installed (along with all the packages above), that’s when the fun stuff happens.

  1. On first boot, Outset will execute the run_chef.sh script (installed by the Outset-ChefClient package). This script will wait for network access, and then use scutil to set the HostName, LocalHostName, and ComputerName to the serial number. Then, it will run execute the first Chef client run with the [x509::munki2_client] recipe, which generates a private key and submits a CSR to the Chef server.

  2. The creation of the CSR file at /etc/ssl/munki2.sacredsf.org.csr triggers the execution of the org.sacredsf.chef.csrtrigger LaunchDaemon (installed by the ChefCSRTrigger package), which will continually run the chef_munki_cert.sh script while that CSR file is present.

  3. On the Chef server/workstation, the CSR needs to be signed (this is assuming the ChefCA is set up according to previous blog posts):
    chef-ssl autosign --ca-name="ChefCA" --ca-path=/home/nmcspadden/chefCA

  4. When the CSR is signed, the LaunchDaemon that is spinning in circles around the CSR file will finally have a successful chef-client run. The successful run will delete the csr file and create the signed certificate file at /etc/ssl/munki2.sacredsf.org.crt.

  5. Once this file exists, the script will then trigger the [munkiSSL::munki] recipe, which copies the certificates and private keys from /etc/ssl/ into /Library/Managed Installs/certs/ with the appropriate names.

  6. Finally, the Munki bootstrap file is created at /Users/Shared/.com.googlecode.munki.checkandinstallatstartup.

  7. The appearance of the Bootstrap file will cause Munki to execute immediately (as we’re still at the Login Window at this point). Munki will read the preferences from the ManagedInstalls profile settings, which tells it to use the certificates in /Library/Managed Installs/certs/ to check the server https://munki2.sacredsf.org/repo for updates.

  8. If the certificates are valid, Munki will proceed with a normal bootstrap run, except through a secure SSL connection that uses its client certificates to communicate with the Munki server, which has been configured to require these certificates (see the previous blog posts).

Conclusion

It’s now possible to securely bootstrap a new OS X machine using Chef to set up SSL client certificates to use with Munki. The best part is that it doesn’t require hands-on attention on the OS X client. The downside is that, at this point, it does require hands-on attention on the Chef server, where the CA is.

There are some possible easy fixes for that, though. The easiest solution would be to run a cronjob on the Chef server that automatically signs all CSRs every X amount of time, which would eliminate any need for manual intervention on the Chef CA. That’s not a desirable method, though, because that’s essentially letting any client who runs the right recipe get a free SSL certificate to the Munki repo. There’s no verification that the client is one we want to allow.

Another possibility to use a more industrial-strength internal CA not managed by Chef, which can have its own policies and methods for signing certificates. This is more common in enterprise environments where they tend to have their own root CAs and Intermediary CAs for internal-only services. More commercial offerings of this sort of thing probably have better methods for determining which CSRs get signed and which don’t.

The chef-ssl client can also be used to generate CSRs for third-party external CAs, but you probably wouldn’t want to sign individual clients with an external CA.

At least we can bootstrap a large batch of machines at once. With 30 machines running, they’ll all submit CSRs and sit there waiting until they get signed. In one command on the CA server, you can sign all 30 CSRs and they’ll automatically proceed to the next step, which is to get the certs and then bootstrap Munki. So we’re at mostly unattended install. But hey, as a proof of concept, it works!

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:

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

Building Munki with Chef in Docker

I’ve previously written about building Munki in Docker, and then going a step further and using Puppet’s SSL client certificates for Munki in Docker. That method allowed us to set up a Munki server in a Docker container that used Puppet’s SSL certificates to offer a secure Munki connection with Puppet clients.

Continuing on the theme of testing out Chef for DevOps, I’d like to try a similar workflow. See my previous blog post about setting up SSL client certificates with Chef.

Once we have a Chef server configured similarly to the aforementioned blogpost, let’s build a Munki Docker container that utilizes Chef.

Chef already has existing containers to build on, but we’re not going to use those. Rather than rebuild an nginx server on top of a container already containing Chef, I’m instead going to add Chef to my existing Munki container.

Start with the basic Munki container:

All that really needs to happen here is to make another Dockerfile that just adds Chef to this. We’re going to steal the method for this from Chef’s existing containers. The new Dockerfile:

The upside to doing it this way: this is how Opscode builds their own Chef containers, so we’re doing exactly what they want and expect. The downside is that we have to install curl just to use it once, during build – and never again. This violates the principles of a small single-purpose Docker container, as I outlined in a previous blog post here. That being said, it’s easier just to do it the way Chef expects and just go with it.

Note the two files that have be to added – client.rb and validation.pem.

Client.rb is straightforward, it’s just the client configuration file for this container, telling it how to access Chef:

The validation.pem is called the Chef validator. It’s the private key that gets created when you create an organization (note that the docs refer to it as file.key instead of file.pem, but it’s the same file). This file needs to be added to all clients before they can register with the Chef server, and this Munki container is no exception. You’ll need to copy your organization’s private key file from the Chef server or workstation to the Docker host to build this image.

As you can see in the client.rb file, I’m specifying the Chef server URL, the name of the validator (based on the organization I created, mentioned above), and enforcing SSL verification – which requires a trusted SSL certificate for your Chef server (meaning you can’t use the self-signed default certificate provided by the Chef server on first run).

With all the pieces in place, it’s time to build the container:
docker build -t "nmcspadden/munki-chef" .

Next post is where we run it!

Setting Up Munki With OS X Yosemite Server

There are many ways to set up Munki, since it’s just a webserver. The Demonstration Setup is a great way to get started, but doesn’t list the steps for setting up OS X Server. A lot of new Munki admins (or generally new Munki admins) may have an OS X Server they have access to, but not other web servers, so a guide to getting started with the latest version of OS X Server (as of writing time, that’s Server 4, on Mac OS 10.10.2 Yosemite) may be helpful.

This is all assuming you’ve got Server.app set up and running.

If the Websites service in Server.app is running, turn it off first.

The first steps are to create the Munki repo in the location that Server 4 uses to store website data:
mkdir /Library/Server/Web/Data/Sites/Default/repo
mkdir /Library/Server/Web/Data/Sites/Default/repo/catalogs
mkdir /Library/Server/Web/Data/Sites/Default/repo/pkgs
mkdir /Library/Server/Web/Data/Sites/Default/repo/pkgsinfo
mkdir /Library/Server/Web/Data/Sites/Default/repo/manifests

Change permissions to make sure it’s accessible:
chmod -R a+rX /Library/Server/Web/Data/Sites/Default/repo

In the Server.app Websites pane, edit the “Server Website” (port 80) settings:
Next to “Redirects”, click “Edit…”, and remove the only redirect (which automatically redirects port 80 to port 443 traffic). It should look like this:
Screen Shot 2015-02-18 at 8.42.17 AM

Next, click “Edit Advanced Settings” and check the box for “Allow folder listing” (just for now – it’s easier to visually test this way):
Screen Shot 2015-02-18 at 8.42.13 AM

Turn the Websites service on.

Open up Safari, navigate to:
http://localhost/repo/

You should see a page like this:
Screen Shot 2015-02-18 at 8.37.41 AM

If you can get to this point, you’ve done the website setup work. Now you can go to the next section:
https://github.com/munki/munki/wiki/Demonstration-Setup#populating-the-repo

Once you’ve populated the repo, you can set up a new manifest called “test_munki_client”. Follow the instructions exactly:
https://github.com/munki/munki/wiki/Demonstration-Setup#creating-a-client-manifest

Go through the Client Configuration section:
https://github.com/munki/munki/wiki/Demonstration-Setup#munki-client-configuration
Here, you need to do two things on your OS X client.

If you are testing this on the OS X Server itself (i.e. you are only using one machine total), do this:
sudo defaults write /Library/Preferences/ManagedInstalls SoftwareRepoURL "http://localhost/repo"
sudo defaults write /Library/Preferences/ManagedInstalls ClientIdentifier "test_munki_client"

If you are testing Munki on a different client machine from the server, do this:
sudo defaults write /Library/Preferences/ManagedInstalls SoftwareRepoURL "http://ip_or_domain_name_of_server/repo"
sudo defaults write /Library/Preferences/ManagedInstalls ClientIdentifier "test_munki_client"

And then finally you can check to see if Munki behaves as you’d expect:
sudo /usr/local/munki/managedsoftwareupdate -vv

Using Chef to Set Up SSL Client Certificates

Previously, I’ve written about setting up Chef Server 12, and then configuring it to use a trusted SSL certificate.

Now, we’re going to go another step, which is to use Chef to generate client certificates by creating a CA, and signing CSRs from clients.

This is based on the documentation for setting up a simple x509 PKI infrastructure with Chef, based on the x509 cookbook. That blog post is very helpful, but is missing some crucial steps, so I’m going to recreate it here and fill in some of the blanks.

Note: Despite my best efforts, I could not get this to work with the default Chef self-signed certificate. This only functioned when I switched to a real SSL certificate. See my previous blog post for a guide.

Set Up the Server:

Most of these instructions will require root or sudo. Also, it’s helpful to use the Ruby provided by Chef, so check which ruby to make sure you’re using the one in /opt/chefdk/embedded/bin/ruby
Let’s go through the process of setting up the x509 cookbook to create a CA for us:

  1. Install the chef-ssl-client gem:
    gem install chef-ssl-client
  2. Install the x509 cookbook:
    knife cookbook site install x509
  3. Replace the x509 cookbook with a fork I created to fix some gaps in the OS X implementation:
    rm -rf chef-repo/cookbooks/x509
    git clone --branch OSX https://github.com/nmcspadden/chef-cookbook-ssl.git x509
  4. Create the ‘certificates’ data bag:
    knife data bag create certificates
  5. Create a test recipe that will install the certificates on the client, located at chef-repo/cookbooks/x509/recipes/chef-ca.rb:
    include_recipe "x509::default"
    
    x509_certificate "chef.sacredsf.org" do
    	certificate "/etc/ssl/chef.sacredsf.org.cert"
    	cacertificate "/etc/ssl/chef_ca.cert"
    	key "/etc/ssl/chef.sacredsf.org.key" 
    	ca "ChefCA" 
    	type "server" 
    	bits 2048 
    	days 365 
    end
    
  6. Now that the recipe is created, we need to upload the cookbooks to the server:
    knife cookbook upload -a
    That command uploads all cookbooks. If you have more than the x509 and vt-gpg cookbooks installed, you can be more precise:
    knife cookbook upload x509 vt-gpg
  7. Finally, create the actual CA. Substitute the ca-path with a valid one of your own:
    chef-ssl makeca --dn '/CN=ChefCA' --ca-path /home/nmcspadden/chefCA
    Enter a passphrase when prompted.

Test It:

Now, it’s time to test it on a Chef client. I tested this on a Mac OS 10.10.2 Yosemite VM, which I set up in the original article about OS X client configuration:
sudo chef-client --force-logger --runlist "recipe[x509::chef-ca]"

You should see output similar to this:

[2015-02-25T13:59:46-08:00] INFO: Processing x509_certificate[chef.sacredsf.org] action create (x509::chef-ca line 3)
[2015-02-25T13:59:46-08:00] INFO: Processing directory[/etc/ssl] action create (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 11)
[2015-02-25T13:59:46-08:00] INFO: directory[/etc/ssl] created directory /etc/ssl
[2015-02-25T13:59:46-08:00] INFO: directory[/etc/ssl] mode changed to 755
[2015-02-25T13:59:46-08:00] INFO: Processing file[/etc/ssl/chef.sacredsf.org.cert] action create (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 17)
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.cert] created file /etc/ssl/chef.sacredsf.org.cert
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.cert] updated file contents /etc/ssl/chef.sacredsf.org.cert
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.cert] owner changed to 0
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.cert] group changed to 0
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.cert] mode changed to 644
[2015-02-25T13:59:46-08:00] INFO: Processing file[/etc/ssl/chef.sacredsf.org.key] action create (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 23)
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.key] created file /etc/ssl/chef.sacredsf.org.key
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.key] updated file contents /etc/ssl/chef.sacredsf.org.key
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.key] owner changed to 0
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.key] group changed to 0
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.key] mode changed to 600
[2015-02-25T13:59:46-08:00] INFO: Processing file[/etc/ssl/chef_ca.cert] action create (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 30)
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef_ca.cert] created file /etc/ssl/chef_ca.cert
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef_ca.cert] updated file contents /etc/ssl/chef_ca.cert
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef_ca.cert] owner changed to 0
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef_ca.cert] group changed to 0
[2015-02-25T13:59:46-08:00] INFO: file[/etc/ssl/chef_ca.cert] mode changed to 644
[2015-02-25T13:59:46-08:00] INFO: Chef Run complete in 4.990408 seconds

Back on the Chef server / workstation, you can check the CSR in the node’s data. If you don’t know the name of the node, run this command to get a list:
knife node list
In my case, my node’s name is TestVM.local.

Check the CSR:
knife node show TestVM.local -a csr_outbox

TestVM.local:
  csr_outbox:
    chef.sacredsf.org:
      ca:   ChefCA
      csr:  -----BEGIN CERTIFICATE REQUEST-----
      [snip]
      -----END CERTIFICATE REQUEST-----
      
      date: 2015-02-25 13:59:46 -0800
      days: 365
      id:   0f68afcc64e13af040bdf9f6b53d4c701c3614d5c6fb1cb7df5751dbbb92d0aa
      key:  
      type: server

Sign It:

Now we can sign the CSR on the server:
chef-ssl autosign --ca-name="ChefCA" --ca-path=/home/nmcspadden/chefCA
Enter the passphrase you used earlier to generate the CA.

This command will search for all outstanding CSRs and ask you to sign them (which is horribly inefficient and requires lots of manual effort for production use, but that’s a future topic for discussion).

Once you have signed all outstanding CSRs (in this example, only one) by entering “yes” when prompted, the run completes:
Saved OK
All CSRs processed.

Signed certificates are saved into the ‘certificates’ data bag:
knife search certificates "host:TestVM.local" -a dn

1 items found

certificates:
  dn: /C=GB/ST=London/L=London/O=Example Ltd/OU=Certificate Automation/CN=chef.sacredsf.org/emailAddress=x509-auto@example.com

To search for all certificates:
knife search certificates "host:*" -a dn

Finally, return to the client and run chef-client again to complete the certificate generation:
sudo chef-client --force-logger --runlist "recipe[x509::chef-ca]"

[2015-02-25T15:02:11-08:00] INFO: Processing x509_certificate[chef.sacredsf.org] action create (x509::chef-ca line 3)
[2015-02-25T15:02:11-08:00] INFO: installing certificate chef.sacredsf.org (id 0f68afcc64e13af040bdf9f6b53d4c701c3614d5c6fb1cb7df5751dbbb92d0aa)
[2015-02-25T15:02:11-08:00] INFO: Processing directory[/etc/ssl] action create (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 11)
[2015-02-25T15:02:11-08:00] INFO: Processing file[/etc/ssl/chef.sacredsf.org.cert] action create (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 17)
[2015-02-25T15:02:11-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.cert] backed up to /var/chef/backup/etc/ssl/chef.sacredsf.org.cert.chef-20150225150211.867169
[2015-02-25T15:02:11-08:00] INFO: file[/etc/ssl/chef.sacredsf.org.cert] updated file contents /etc/ssl/chef.sacredsf.org.cert
[2015-02-25T15:02:11-08:00] INFO: Processing file[/etc/ssl/chef.sacredsf.org.key] action nothing (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 23)
[2015-02-25T15:02:11-08:00] INFO: Processing file[/etc/ssl/chef_ca.cert] action create (/var/chef/cache/cookbooks/x509/providers/certificate.rb line 30)
[2015-02-25T15:02:11-08:00] INFO: file[/etc/ssl/chef_ca.cert] backed up to /var/chef/backup/etc/ssl/chef_ca.cert.chef-20150225150211.873229
[2015-02-25T15:02:11-08:00] INFO: file[/etc/ssl/chef_ca.cert] updated file contents /etc/ssl/chef_ca.cert
[2015-02-25T15:02:11-08:00] INFO: Chef Run complete in 2.903235 seconds

You can use openssl to verify your certificate:
openssl x509 -in /etc/ssl/chef.sacredsf.org.cert -issuer | head -1

issuer= /CN=ChefCA

Conclusions

Chef now provides a simpler way of generating client certificates than creating your own CA using openssl. However, there’s still some major pitfalls here that get in the way of a strong SSL client certificate solution.

For one thing, signing the CSRs is currently done manually. Ideally, I’d like to set up some kind of automatic script to sign the CSRs based on some policy, so clients can be approved without manual intervention.

Another issue is that because the CSRs require manual approval, it currently takes two chef-client runs to get an SSL cert. The next goal is to get this step down to being more automatic and streamlined.