I'm developing a private macOS application on an Apple-silicon Mac mini running macOS Golden Gate 27.2.
I'm evaluating a system LaunchDaemon running under a dedicated, non-administrator identity that cannot log in interactively. A separate desktop application would communicate with the daemon through authenticated local IPC.
Before implementing this architecture, I would like to establish the officially supported approach.
My main question:
What is the Apple-supported method for creating and maintaining a dedicated, non-administrator, non-interactive service identity for a system LaunchDaemon on macOS 27.2?
In particular:
- Is there a documented procedure for creating, restricting, disabling, recovering and eventually removing this identity?
- Are
dscl,sysadminctlor another account-management mechanism supported for this purpose? - Can a LaunchDaemon run under this identity without an interactive user login, including when registered through
SMAppService? - Are there any secure-token, FileVault, volume-ownership or keychain restrictions that would affect this design?
If a dedicated non-login service identity is not a supported architecture, what alternative does Apple recommend?
I'd particularly appreciate links to official Apple documentation or guidance from Apple engineers.
Thank you.
macOS’s support for role accounts is not particularly strong. There are (at least :-) four parts to this:
- Creating the role account.
- Using the role account in your
launchddaemon. - Handling the role account execution context.
- Keeping this running in the long term.
I’ll deal with each in turn.
The only way to specifically create a role account is by passing the -roleAccount flag to sysadminctl. This isn’t really an API — I mean, it doesn’t even have a man page )-: — but it’s the best we’ve got.
Another option is Identity Services, both the CBIdentity class within the Collaboration framework and the CSIdentity type within the OSServices framework. These let you create hidden users, and the doc comments for the underlying flag, kCSIdentityFlagHidden, make it pretty clear that hidden users are intended to be used for system services. Specifically, in <OSServices/CSIdentity.h> you’ll find this:
/*
* This flag causes the identity to be "hidden," that is, excluded
* from most user-visible identity lists. Hidden identities include
* administrative users and groups such as root, www, and mysql.
* System service access control groups should be created with the
* hidden flag.
*/
kCSIdentityFlagHidden = 1
However, the whole Identity Services concept is pretty much deprecated, either formally or informally.
Configuring a launchd daemon to use a role account is trivial: Set the UserName property in the launchd property list, as documented in the launchd.plist man page.
There are a couple of thinsg to watch out for here. First, we generally recommend that you install your launchd daemon using SMAppService, and that requires you to pass it the name of a launchd property list within your app’s bundle. That’s a read-only location, so you must know UserName in advance. And that means that, in the previous step, you have to pick a user name that’s not going to collide with any other product that does this. Usually that means embedding your product or company name into the user name.
Second, role accounts have a tendency to disappear. This might happen, for example, if the user re-installs the OS, or uses Migration Assistant to move from one Mac to the other. A product that uses one has to handle this. Typically that means detecting the problem and offering a way to repair it.
A launchd daemon always runs in the global context, as explained in TN2083 Daemons and Agents. If you customise the UserName property, the daemon ends up running in a mixed context. The user ID no longer matches the user ID associated with the context as a whole, which for the global context is, of course, root.
Running in a mixed execution context is tricky. You’ll find things that work and things that fail, and that list can change over time. For example, I recently learnt that enabling the App Sandbox in such a daemon doesn’t work.
All of this means that role accounts tend to be brittle. You end up relying on a bunch of implementation details. As those can change over time, you have to update your product accordingly. So, it’s debatable whether role accounts are worth the effort on macOS. It might make more sense to run your daemon as root but limit its scope by enabling App Sandbox.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"