Managing user accounts with SecureTokens in Addigy

Addigy natively doesn't support the retention of Secure Tokens when changing a deployed user password, so it's up to us to do it ourselves.

Managing user accounts with SecureTokens in Addigy

SUMMARY

In order to plug some 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.

1 – Introduction
2 – Variables
3 – Tools
4 – Logic

1 – INTRODUCTION

Managing a Mac fleet often necessitates deploying an administrator account to your devices. This account may be leveraged by your help desk for endpoint support, or it may be used by automation scripts 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 major 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 just change the value for the password and save, Addigy will push it out immediately to any policies or devices you had scoped. If those admin accounts had ever been given a Secure Token (or “volume ownership” status on Apple silicon computers), that will be lost during the password change. This is because updating a user password this way will update 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 set out to create a more powerful 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 you need to bring to the table for success:

targetAccount="acme_admin"
Short name of account to deploy, field cannot be blank
targetAccountFullName="Acme Admin"
Full name for account
targetAccountIsAdmin="true"
Should target account be an admin? Set to “false” if not.
targetAccountPW="My desired password, not in plaintext"
Desired password for account
targetAccountOldPW="My previous password, not in plaintext"
Old password, needed if password is being rotated. If there is no previous password, leave this blank
knownAdmin="theOtherAdmin"
Short name of existing account to pass a token to targetAccount
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 in the event 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 properly obfuscated from logs and prying eyes. Never transmit sensitive passwords as plaintext to your workstations. I’ve recently authored a post about how to use one of Addigy’s built-in binaries to securely pass variables in a limited way.

3 – TOOLS

Next, let’s walk through our tools and how they’re used in the workflow to create a more effortless user deployment and password updating workflow. Keep an eye out for the variables we defined above.

data_key_client

In order for any of this to work, we first 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 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 a way to use an existing Addigy binary to handle the same functions.

The 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 a little creative to figure out how to feed it values to be used in 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.

user-manager

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 when 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 key 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.

/Library/Addigy/user-manager -create -username="$targetAccount" -full-name="$targetAccountFullName" -password="$targetAccountPW" -admin="$targetAccountIsAdmin"
Create a user account
/Library/Addigy/user-manager -update-password -username="$targetAccount" -password="$targetAccountPW"
Update an existing password (does not retain SecureToken)

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.

fdesetup

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.

sudo fdesetup list 2> /dev/null | grep "$targetAccount"
See if a user has a SecureToken
sudo fdesetup remove -user "$targetAccount" 
Remove an existing user’s SecureToken

dscl

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.

sudo dscl . list /Users | grep -q "$targetAccount"
Search for a user account
sudo dscl . authonly “$targetAccount” "$targetAccountPW"
Test a password against an account

sysadminctl

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

sysadminctl -adminUser $knownAdmin -adminPassword "$knownAdminPW" -secureTokenOn $targetAccount -password "$targetAccountPW"
Pass a SecureToken from one user to another
sysadminctl -adminUser "$targetAccount" -adminPassword
     "$targetAccountOldPW" -resetPasswordFor "$targetAccount"
     -oldPassword "$targetAccountOldPW" -newPassword
     "$targetAccountPW"
Update a password for an account while retaining an existing SecureToken

4 – LOGIC

With all of this defined, we can now examine the logic of how to put these pieces together into a functioning user deployment and password rotation workflow.

Collect Data

Before acting on anything, we populate a series of booleans to decide what action will be taken on the computer:

targetAccountExists=""
Set to true if we get a return from a dscl check to see if the account exists
targetAccountHasToken=""
Set to true if the account is part of an fdesetup list check for accounts with a SecureToken
targetAccountHasWorkingPassword=""
Set to true if we get a passing result from checking the user account and provided password using dscl authonly
targetAccountHasWorkingPasswordOld=""
Set to true if we get a passing result from checking the user account and provided old password using dscl authonly
knownAdminExists=""
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)
knownAdminHasToken=""
Set to true if the known admin account is part of an fdesetup list check for accounts with a SecureToken
knownAdminHasWorkingPassword=""
Set to true if we get a passing result from checking the known admin account and provided password using dscl authonly

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-manager will 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 a working old password, use sysadminctl to 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 remove before changing the password

By using these data collection tools and logic gates, we can do everything in our power to retain SecureTokens while updating passwords, safely expunge SecureTokens that would be corrupted during other password rotation workflows, and deploy new admin accounts while taking advantage of existing ones. In the past, we had set up a lengthy process for this that we’d have to trigger over the course of a week or two (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 this new workflow, it’s a single script that we can update when required and walk away from, confident in knowing it will do everything it can to retain SecureToken.