launchctl LaunchDaemons and keychain access

Hi everyone,

I'm struggling to access a certificate stored in keychain when using LaunchDaemons and would appreciate any advise to improve over my current workaround with an agent.

I'm maintaining an M1-based Mac Mini with macOS Big Sur 11.4 as a build client for Gitlab CI. The machine makes itself known to the orchestrating Gitlab server through the so-called gitlab-runner process which regularly polls the Giltab server for build job requests. It would then eventually call xcodebuild to generate artifacts and upload them to Gitlab.

In order to have this process startup automatically, I've loaded it through a /Library/LaunchDaemons/gitlab-runner.plist file in launchctl. This setup has been working greatly for months, making the build client immediately available after a reboot without the need to log into it.

I've now started to deploy codesign tasks to the build client and encountered issues with keychain access to the certificate. I installed the certificate and performed security find-identity -v -p codesigning in the Terminal to validate correct installation. I have ensured that the codesign tasks succeed when performed through Terminal manually. A popup was shown on first try about codesign wanting to access to the local keychain, which I always allowed. Codesign then reported success: signed Mach-O universal. The behavior is different for my LaunchDaemons-based codesign tasks (spawned under the same user).

I've tried multiple permutations for sudo launchctl load -w /Library/LaunchDaemons/gitlab-runner.plist:

SessionCreate=true, local keychain: FAIL (errSecInternalComponent)
SessionCreate=true, System keychain: FAIL (errSecInternalComponent)
SessionCreate=false, local keychain: FAIL (error: The specified item could not be found in the keychain.)
SessionCreate=false, System keychain: FAIL (errSecInternalComponent)

I've then tried a user agent launchctl load -w ~/Library/LaunchAgents/gitlab-runner.plist:

SessionCreate=true, local keychain: FAIL (errSecInternalComponent)
SessionCreate=false, local keychain: OK (Popup -> Always allow, signed Mach-O universal)

So apparently, the last variant works reliably. However, it has the severe downside that I now need to log in the user after a reboot in order to kick off the agent.

Is there a way to make this work with /Library/LaunchDaemons, possibly through some plist property? As a test I've tried to "Allow all applications" to access the private key associated with the Certificate but that didn't change anything.

Best regards

Maik

Is there a way to make this work with /Library/LaunchDaemons, possibly through some plist property?

The problem here is that you are using a daemon, which uses a system context, whereas an agent uses a user context. For this reason you can run into all sorts of strange issues with access a user level Keychain and other edge cases with code signing and the Security framework. My recommendation would be to use an agent, if you must do this, to make sure you can execute from a user context.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Hi Matt,

thank you for your advise. I would like to note that I am setting the user in the job properties.

<key>UserName</key>
<string>dev</string>

Apparently, this is not enough to setup up the proper context. Could you elaborate please how I can achieve this?

I prefer a solution that automatically starts up after reboot to minimize the maintenance effort. As an alternative, is there a way to pass the certificate / signing identity differently to the codesign executable? Or would it have to reside in a keychain?

Best regards

Maik

Maik,

I would like to note that I am setting the user in the job properties.

<key>UserName</key>
<string>dev</string>

Right, so when you do this you are running a system daemon in a global context, acting as a specific user. I suspect this is what is causing you the issues here. To make you code work in a user context you will need to run as an agent.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Thank You Matt,

I might just stick with the agent for now.

For completeness, I've been toying with ways to avoid the agent. When I unlock the default keychain prior to codesigning, it seems to work, i.e.

security unlock-keychain -p <PASSWORD>

I would like to note the security implications, potentially giving access to all information inside the keychain to all kinds of tasks.

Best regards

Maik

Using Xcode 12.5 and 13, unlocking the keychain from a non GUI session (i.e. SSH) does not work anymore. codesign fails with errSecInternalComponent

I filed a RADAR report to Apple https://feedbackassistant.apple.com/feedback/9642986

@maikschulze what is the Xcode and macOS version you're using ? For me "security unlock-keychain" does not work on global context (when I SSH to the instance, no GUI session whatsoever) I Tried with macOS 11.5, 11.6 and Xcode 12.5.1 and Xcode 13

I lost days to this, but finally found a solution. https://stackoverflow.com/questions/47582989/launchd-not-able-to-access-mac-os-keychains

Add this to your plist

&lt;key>SessionCreate&lt;&#x2F;key>
&lt;true&#x2F;>

Our LaunchDaemon can run xcode builds, and finds a valid identity to sign with now.

Also had to add the Apple Worlwide Developer Relation cert to the system keychain

if [ ! -f .&#x2F;AppleWWDRCAG3.cer ]; then
    echo "Downloadind Apple Worlwide Developer Relation GA3 certificate"
    curl -s -o .&#x2F;AppleWWDRCAG3.cer https:&#x2F;&#x2F;www.apple.com&#x2F;certificateauthority&#x2F;AppleWWDRCAG3.cer
fi
echo "Installing Apple Worlwide Developer Relation GA3 certificate into System keychain"
SYSTEM_KEYCHAIN=&#x2F;Library&#x2F;Keychains&#x2F;System.keychain
sudo security import .&#x2F;AppleWWDRCAG3.cer -t cert -k "${SYSTEM_KEYCHAIN}"
launchctl LaunchDaemons and keychain access
 
 
Q