Summary:
Setting up Jenkins on OS X has been made significantly easier with the most recent installer (as of 1.449 - March 9, 2012), however managing the process of code signing is still very difficult with no straightforward answer.
Motivation:
Run a headless CI server that follows common best practices for running services on OS X (Some of which is explained here in plain language).
Background:
- October 12, 2009 - How to automate your iPhone app builds with Hudson
- June 15, 2011 - Jenkins on Mac OS X; git w/ ssh public key
- June 23, 2011 - Continuous Deployment of iOS Apps with Jenkins and TestFlight
- July 26, 2011 - Missing certificates and keys in the keychain while using Jenkins/Hudson as Continuous Integration for iOS and Mac development
- August 30, 2011 - Xcode Provisioning File not found with Jenkins
- September 20, 2011 - How to set up Jenkins CI on a Mac
- September 14, 2011 - Getting Jenkins Running on a Mac
- November 12, 2011 - Howto: Install Jenkins on OS X and make it build Mac stuff
- January 23, 2012 - Upcoming Jenkins OSX installer changes
- March 7, 2012 - Thanks for using OSX Installer
Process:
Install Jenkins CI via OS X installer package. For the "Installation Type" step, click the Customize button, and choose "Start at boot as 'jenkins.'"
Discussion:
The naive expectation at this point was that a free-style project with the build script xcodebuild -target MyTarget -sdk iphoneos
should work. As indicated by the title of this post, it does not and fails with:
Code Sign error: The identity 'iPhone Developer' doesn't match any valid certificate/private key pair in the default keychain
It is obvious enough what needs to happen - you need to add a valid code signing certificate and a private key into the default keychain. In researching how to accomplish this, I have not found a solution that doesn't open up the system to some level of vulnerability.
Problem 1: No default keychain for jenkins daemon
sudo -u jenkins security default-keychain
...yields "A default keychain could not be found"
As pointed out below by Ivo Dancet, the UserShell is set to /usr/bin/false for the jenkins daemon by default (I think this is a feature, not a bug); follow his answer to change the UserShell to bash. You can then use sudo su jenkins
to get logged in as the jenkins user and get a bash prompt.
sudo su jenkins
cd ~/Library
mkdir Keychains
cd Keychains
security create-keychain <keychain-name>.keychain
security default-keychain -s <keychain-name>.keychain
Okay, great. We've got a default keychain now; let's move on right? But, first why did we even bother making a default keychain?
Almost all answers, suggestions, or conversation I read throughout researching suggest that one should just chuck their code signing certs and keys into the system keychain. If you run security list-keychains
as a free-style project in Jenkins, you see that the only keychain available is the system keychain; I think that's where most people came up with the idea to put their certificate and key in there. But, this just seems like a very bad idea - especially given that you'll need to create a plain text script with the password to open the keychain.
Problem 2: Adding code signing certs and private key
This is where I really start to get squeamish. I have a gut feeling that I should create a new public / private key unique for use with Jenkins. My thought process is if the jenkins daemon is compromised, then I can easily revoke the certificate in Apple's Provisioning Portal and generate another public / private key. If I use the same key and certificate for my user account and Jenkins, then it means more hassle (damage?) if the jenkins service is attacked.
Pointing to Simon Urbanek's answer you'll be unlocking the keychain from a script with a plain text password. It seems irresponsible to keep anything but "disposable" certificates and keys in the jenkins daemon's keychain.
I am very interested in any discussion to the contrary. Am I being overly cautious?
To make a new CSR as the jenkins daemon in Terminal I did the following...
sudo su jenkins
certtool r CertificateSigningRequest.certSigningRequest
You'll be prompted for the following (most of these I made educated guesses at the correct answer; do you have better insight? Please share.)...
- Enter key and certificate label:
- Select algorithm:
r
(for RSA)
- Enter key size in bits:
2048
- Select signature algorithm:
5
(for MD5)
- Enter challenge string:
- Then a bunch of questions for RDN
- Submit the generated CSR file (CertificateSigningRequest.certSigningRequest) to Apple's Provisioning Portal under a new Apple ID
- Approve the request and download the .cer file
security unlock-keychain
security add-certificate ios_development.cer
This takes us one step closer...
Problem 3: Provisioning profile and Keychain unlocking
I made a special provisioning profile in the Provisioning Portal just for use with CI in hopes that if something bad happens I've made the impact a little smaller. Best practice or overly cautious?
sudo su jenkins
mkdir ~/Library/MobileDevice
mkdir ~/Library/MobileDevice/Provisioning Profiles
- Move the provisioning profile that you setup in the Provisioning Portal into this new folder. We're now two short steps away from being able to run xcodebuild from the the command line as jenkins, and so that means we're also close to being able to get the Jenkins CI running builds.
security unlock-keychain -p <keychain password>
xcodebuild -target MyTarget -sdk iphoneos
Now we get a successful build from a command line when logged in as the jenkins daemon, so if we create a free-style project and add those final two steps (#5 and #6 above) we will be able to automate the building of our iOS project!
It might not be necessary, but I felt better setting jenkins UserShell back to /usr/bin/false after I'd successfully gotten all this setup. Am I being paranoid?
Problem 4: Default keychain still not available!
(EDIT: I posted the edits to my question, rebooted to make sure my solution was 100%, and of course, I'd left out a step)
Even after all the steps above, you'll need to modify the Launch Daemon plist at /Library/LaunchDaemons/org.jenkins-ci.plist as stated in this answer. Please note this is also an openrdar bug.
It should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>JENKINS_HOME</key>
<string>/Users/Shared/Jenkins/Home</string>
</dict>
<key>GroupName</key>
<string>daemon</string>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>org.jenkins-ci</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Library/Application Support/Jenkins/jenkins-runner.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>UserName</key>
<string>jenkins</string>
<!-- **NEW STUFF** -->
<key>SessionCreate</key>
<true />
</dict>
</plist>
With this setup, I would also recommend the Xcode plugin for Jenkins, which makes setting up the xcodebuild script a little bit easier. At this point, I'd also recommend reading the man pages for xcodebuild - hell you made it this far in Terminal, right?
This setup is not perfect, and any advice or insight is greatly appreciated.
I have had a hard time selecting a "correct" answer since what I've come to use to solve my problem was a collection of just about everyone's input. I've tried to give everyone at least an up vote, but award the answer to Simon because he mostly answered the original question. Furthermore, Sami Tikka deserves a lot of credit for his efforts getting Jenkins to work through AppleScript as a plain ol' OS X app. If you're only interested in getting Jenkins up and going quickly within your user session (i.e. not as a headless server) his solution is much more Mac-like.
I hope that my efforts spark further discussion, and help the next poor