To plug holes left by Addigy’s default user deployment mechanisms, we assembled a script that can deploy new user accounts to a Mac or change their passwords while retaining existing SecureTokens or fetching them from an existing, known admin account.
This article was originally authored and posted by Ross Matsuda at sudoade.com.
1 – INTRODUCTION
Managing a Mac fleet often necessitates deploying an administrator account to your devices. Your help desk may leverage this account for endpoint support, or automation scripts may use it to maintain and enhance your workstations. Keeping this account secure is of paramount importance. To prevent employees or external actors from compromising your Macs you must be able to rotate this password, quickly and securely, to a new value at a moment’s notice.
Addigy offers a native tool to deploy user accounts to Macs via their legacy Profiles (MCX) interface. You type in the desired username, full name, and password, check a box if it should be an administrator, and then you can scope that user to any of your devices or policies. A significant downside to this method of user deployment comes into focus if you need to change the password for an account deployed in this manner. If you change the value for the password and save it, Addigy will push it out immediately to any policies or devices you had scoped.
If those admin accounts were ever given a Secure Token (or “volume ownership” status on Apple silicon computers), that will be lost during this sort of password change. Updating a user password this way will alter the user account’s login password but not update the local keychain (generally not a problem for these deployed admin accounts) or the FileVault user database. Once the FileVault password and login password are out of sync, the user account is left in an awkward state — it still believes it has a SecureToken, but the password used to decrypt the disk is now different than the one it expects, rendering it unusable.
With these limitations in mind, I created a more robust method of deploying user accounts leveraging Addigy’s existing tools. The script’s workflow looks like this:
- Check to see if the account we want to build exists.
- Once created, see if there’s another existing admin to pass it a SecureToken.
- If the account already exists, check the password to see if it needs to be updated.
- If the password needs to be updated, change it, retaining an existing SecureToken if present.
2 – VARIABLES
To get started, let’s walk through the variables required for success:
Short name of the account to deploy, please note field cannot be blank.
Full name of the account to deploy.
Should the target account be an admin? Set to “false” if not.
targetAccountPW="My desired password, not in plaintext"
Desired password for the account.
targetAccountOldPW="My previous password, not in plaintext"
The last known password for the deployed account. This is needed to try and retain SecureToken if the account already exists on workstations and the password is being rotated. If no previous password is known, leave this blank.
Short name of an existing, deployed account to pass a SecureToken to target account.
knownAdminPW="This other account's password, not in plaintext"
Password for knownAdmin
The notable differentiators here are the inclusion of credentials for a known account to check and an “old password” variable for us to compare against if a password needs to be rotated from a deprecated value to a new one. By providing a known admin account, we can test it for a SecureToken and funnel that to our new or updated account. If there isn’t a known account to leverage, as long as we have the last working password for the already-deployed account, we can use that password to ensure the existing SecureToken is retained.
Important note: only use this workflow if you have some method of making sure the password you’re going to pass is obfuscated from logs and prying eyes. Never transmit sensitive passwords as plaintext to your workstations.
3 – TOOLS
Next, let’s walk through our tools and how they’re used to create a more effortless user deployment and password updating workflow. Keep an eye out for the variables we defined above.
For this to work, we need a method of getting a password safely to a computer without it being displayed or recorded in plaintext. Addigy recommends the online tool Vault (https://www.vaultproject.io) to handle this, and while we haven’t gone through such a deployment, it looks like a spectacular solution. Instead of relying on an external tool, we discovered how to use an existing Addigy binary to handle the same functions.
data_key_client is responsible for taking a secret you enter into Addigy (here, a password), assigning it a random alphanumeric
fileIdentifier value, and then translating requests for that
fileIdentifier into plaintext when called. What’s more, these
fileIdentifier values are local to your Addigy instance — no other Addigy customer will be able to decode your secrets; they can only be used within your environment.
As of October 2021, there is no GUI for passing values to the
data_key_client, so we had to get creative to feed values to our automation scripts. I’ll be covering this in more detail in another article. For today, what matters is that we have a mechanism to send passwords to endpoints in a secure way.
Next up is Addigy’s
user-manager, which they leverage for their existing account creation methods. This binary has a lot of great functionality, including built-in logic for identifying if an account already exists or not and comparing passwords. You can even encode passwords directly in this tool to be called on other workstations.
One fundamental limitation of
user-manager is that it currently doesn’t have any method to unscramble an encoded password and output it for use in a script or custom software. This limits the versatility of any passwords stored in
user-manager, so we won’t be using those functions in our script.
Create a user account
/Library/Addigy/user-manager -create -username="$targetAccount" -full-name="$targetAccountFullName" -password="$targetAccountPW" -admin="$targetAccountIsAdmin"
Update an existing password (does not retain SecureToken)
/Library/Addigy/user-manager -update-password -username="$targetAccount" -password="$targetAccountPW"
In this workflow, we take advantage of
user-manager to handle account creation, including setting administrator status and updating a password if we know the SecureToken cannot be retained.
This is Apple’s tool for managing FileVault disk encryption. It can perform a wide variety of actions, but we only really care about two: asking for a list of users with SecureToken and stripping a SecureToken from a given account.
See if a user has a SecureToken
sudo fdesetup list 2> /dev/null | grep "$targetAccount"
Remove an existing user’s SecureToken
sudo fdesetup remove -user "$targetAccount"
We’ll be using
dscl to learn about and act upon local user accounts on a managed Mac. Like
fdesetup, we’re looking at a fraction of what it can do: getting a list of user accounts on a Mac and seeing if we have working passwords for those accounts.
Search for a user account
sudo dscl . list /Users | grep -q "$targetAccount"
Test a password against an account
sudo dscl . authonly “$targetAccount” "$targetAccountPW"
The reason any of this workflow is possible is due to the features of
sysadminctl. Using it, we can pass SecureTokens from one account to another or non-destructively change a user password while retaining an existing SecureToken.
Pass a SecureToken from one user to another
sysadminctl -adminUser $knownAdmin -adminPassword "$knownAdminPW" -secureTokenOn $targetAccount -password "$targetAccountPW"
Update a password for an account while retaining an existing SecureToken
sysadminctl -adminUser "$targetAccount" -adminPassword "$targetAccountOldPW" -resetPasswordFor "$targetAccount" -oldPassword "$targetAccountOldPW" -newPassword "$targetAccountPW"
4 – LOGIC
With everything defined, we can examine the logic of putting the pieces into a functioning user deployment and password rotation workflow.
Before acting on anything, we populate a series of booleans to decide what action will be taken on the computer:
Set to true if we get a return from a
dscl check to see if the account exists.
Set to true if the account is part of an
fdesetup list check for accounts with a SecureToken.
Set to true if we get a passing result from checking the user account and provided password using
Set to true if we get a passing result from checking the user account and provided old password using
Set to true if we get a return from a
dscl check to see if a known admin account exists. Include a manual override of this value if it is not provided (if you don’t have a known admin account you want to leverage, set the value to something other than null to avoid errors).
Set to true if the known admin account is part of an
fdesetup list check for accounts with a SecureToken.
Set to true if we get a passing result from checking the known admin account and provided password using
Act based on results
Now that we have all the information we need, we act based on different combinations of variables.
- If the account doesn’t exist yet,
user-managerwill create it.
- If we have a working known admin account with a verified password and SecureToken, pass it to the new account.
- If the account already exists, see if the password is valid or not.
- If the password is valid, make sure the account has a SecureToken. If it doesn’t, but the known admin is detected with a verified password and SecureToken, pass the account a SecureToken before exiting the script.
- If the password is invalid and we have a known admin with a verified password and SecureToken, strip the existing token from the account, change the password, then pass the SecureToken back from the known admin.
- If the password is invalid and we have an old working password, use
sysadminctlto non-destructively rotate the password using both new and old passwords.
- If the password is invalid and we have no method to retain a SecureToken, remove the SecureToken via
fdesetup removebefore changing the password.
By using these data collection tools and logic gates, we can retain SecureTokens while updating passwords, safely expunge SecureTokens that get corrupted during other password rotation workflows, and deploy new admin accounts while taking advantage of existing ones.
Previously, we used a lengthy process that ran over a week (or two) and looked like this: halt further deployment of the admin account, deploy a new admin account with a new password, deploy a custom software to move SecureToken from one admin to the other, deploy a custom software to delete the old admin account.
With our new workflow, it’s a single script that we can update when required and walk away from, confident in knowing it will do everything possible to retain SecureToken.