Thinking About Extension Attributes

brock_walters
Contributor

One great thing about JAMF Nation is that awesome workflows can be shared so easily. Sometimes, however, ones that are slightly less than optimal get propagated. The other day I happened to come across an instance of this that I thought maybe I could help explain.

The extension attribute in question:

#!/bin/sh
echo "<result>$(dscl . list /Users UniqueID | awk '$2>500{print $1}' | wc -l | sed 's/^ *//g')</result>"

First, counting lines with wc is probably not the best way to count the members of what should be declared as an array.

On my computer this command yields 2 results:

brock$ dscl . list /Users UniqueID | awk '$2>500{print $1}'
brock
test

Those 2 results can be captured in an array:

array=($(dscl . list /Users UniqueID | awk '$2>500{print $1}'))

& counted like this:

brock$ echo "${#array[@]}"
2

Second, the 1-liner above does not handle common cases you would want to capture with an extension attribute like this:

• an end user creating an unauthorized user account with a UID under 500 in the GUI
• authorized user accounts created by an application or by Server.app with UID under 500

If your organization has determined that something like this is important... think about the deployment. For example, maybe you know that:

• all computers are required to run the most current version of the OS
• the user account assigned to UID 501 is created programmatically during provisioning or by IT staff
• user accounts created by an end user in the GUI or by Server.app other than UID 501 are unauthorized
• end users do not know how or do not have privileges to change the UID for their user account

With these assumptions something can be built that will result in getting good information to help remediate the problem.

The current user accounts on the managed computer are needed for a comparison to a list of known-good, authorized user accounts - often. This is really the only way to ensure that the user accounts that exist are authorized or not.

One idea might be to include the following script as a payload in your Update Inventory policy in the JSS:

#!/bin/bash

# jss.authusr.sh
# ©2016 brock walters jamf

# this script works under the following assumptions:
#   1) the user account assigned to UID 501 is created programmtically during provisioning or by IT staff
#   2) user accounts created by an end user in the GUI or by Server.app other than UID 501 are unauthorized
#   3) end users do not know how or do not have privileges to change the UID for their user account

# this script can be used in 1 of the following 3 ways:
#   - adding a comma-separated or space-separarted list to the parameter 4 field when using the script as the
#     payload in a JSS policy
#   - passing a comma-separated or space-separarted list as argument 4 of the command to run the script
#   - hard-coding a comma-separated or space-separarted list into the "AuthorizedUserAccounts" variable below
#     eg, AuthorizedUsers="_secretITaccount,mattdaemon,unclenobody" etc...

AuthorizedUserAccounts=""

#########################################
########## DO NOT MODIFY BELOW ##########
#########################################

AuthorizedUserAccounts="${AuthorizedUserAccounts:-$4}"
addauth=($(echo "$AuthorizedUserAccounts" | /usr/bin/sed 's/,/ /g'))
sysauth=(daemon Guest nobody root _amavisd _appleevents _appowner _appserver _ard _assetcache _astris _atsserver _avbdeviced _calendar _ces _clamav _coreaudiod _coremediaiod _cvmsroot _cvs _cyrus _devdocs _devicemgr _displaypolicyd _distnote _dovecot _dovenull _dpaudio _eppc _ftp _geod _iconservices _installassistant _installer _jabber _kadmin_admin _kadmin_changepw _krb_anonymous _krb_changepw _krb_kadmin _krb_kerberos _krb_krbtgt _krbfast _krbtgt _launchservicesd _lda _locationd _lp _mailman _mcxalr _mdnsresponder _mysql _netbios _netstatistics _networkd _nsurlsessiond _nsurlstoraged _postfix _postgres _qtss _sandbox _screensaver _scsd _securityagent _serialnumberd _softwareupdate _spotlight _sshd _svn _taskgated _teamsserver _timezone _tokend _trustevaluationagent _unknown _update_sharing _usbmuxd _uucp _warmd _webauthserver _windowserver _www)
usercat=("${addauth[@]}" "${sysauth[@]}")
echo "${usercat[@]}" | /usr/bin/base64 > /private/tmp/authusr.b64

This includes the user account names of all the underscore system user accounts & the known non-underscore user accounts created by the OS: daemon, Guest, nobody, root. The idea is that these user accounts are "authorized" so they should be excluded from your search for unauthorized user accounts.

The script also lets you add more authorized user accounts specific to your environment in the parameter 4 field of the script settings in the JSS or by passing in a list to argument 4 when you execute the script or by hard-coding them into the "AuthorizedUserAccounts" variable.

These authorized user accounts are written to a temporary file which is encoded. You may think this is fussy - it could be approached differently. My thought was - I don't want my average end user to know which user accounts are authorized or not. I also don't want them to be able to very easily add an "authorized" user account of their own to the list!

Maybe your organization creates an account on all computers called "superadmin".

43b9826c79174bb986f2eac3217df59b
You could populate the script as follows to exempt this authorized user account:

f61ba3fa6c1b4c588c736d6ba22b0c1c
The 2nd part of my strategy would be an extension attribute that looked at the comparison between the authorized & unauthorized user accounts. Since the script above would run every time the computers submitted inventory the extension attribute would be updated immediately every time that happened.

#!/bin/bash

# jss.xat.authusr.sh
# ©2016 brock walters jamf

# used in conjunction with a script to create a list of known-good authorized users this JSS extension
# attribute will display "No Unauthorized User Accounts" if 0 non-system or unauthorized accounts exist.
# the value can be used as a JSS Smart Group criteria, eg, "Unauthorized User Accounts" IS "No Unauthorized
# User Accounts". if the variable "UnauthorizedUserAccounts" is populated the unauthorized user account UIDs
# will populate the JSS extension attribute field instead, eg, 250 502 503 504...

# read list of user accounts, exempt authorized users, convert user account names to UID

authusr=($(/usr/bin/base64 -D -i /private/tmp/authusr.b64))
usrxmpt=$(for i in "${authusr[@]}";do /bin/echo -n "/^$i$/d;";done)
userlst=($(/usr/bin/dscl . -list /Users | /usr/bin/sed "$usrxmpt")) 
useruid=($(for j in "${userlst[@]}";do /usr/bin/id -u "$j";done))

# conditional check for unauthorized users

UnauthorizedUserAccounts=($(echo "${useruid[@]//501/}"))
if [[ -z ${UnauthorizedUserAccounts[@]} ]]
then
    echo "<result>No Unauthorized User Accounts</result>"
else
    echo "<result>${UnauthorizedUserAccounts[@]}</result>"
fi
/bin/rm -f /private/tmp/authusr.b64

The extension attribute script is reading the temporary list of known-good, authorized user accounts, comparing that to the current user accounts on the computer, exempting the authorized accounts, then populating the extension attribute field. If the result is "No Unauthorized User Accounts", you're golden.

4eb4158fd0354b028421fd90c3379f94

Otherwise, the extension attribute is populated with the UID of the unauthorized user accounts.

b2d276335298408299a42537d3fb18fc

Having the UID will let you take further action if need be to get rid of bob's potentially hidden account or fred's account.

588b532f996548458e6b492c9826b8da
68b612bbce004c11951a459a93bbef7d

I hope this is helpful. Feel free to nitpick my solution to death or ask questions! Thanks!

2 REPLIES 2

peterlbk
Contributor

Hi @brock.walters ,

just ran a test of this wonderful script on my 10.11 - 10.12 test machines.

I added a few accounts to your sysauth list - do you think these need to be added to the general list?

_applepay _mbsetupuser _captiveagent _ctkd _datadetectors _findmydevice _gamecontrollerd _hidd _mobileasset _ondemand _wwwproxy _xcsbuildagent _xcscredserver _xserverdocs

brock_walters
Contributor

Yes. My list did not include the users added in macOS Sierra 10.12. You can add whatever user accounts in that list you need or simply add them to the parameter field as needed. Part of the reason a generic solution won't work (ie, the bad extension attribute example at the top) is that everyone's environment may have different authorized user accounts or the authorized user acccounts may change over time or there may be different accounts needed for different parts of your deployment. This workflow is modular - you could have different versions of the Update Inventory policy with different user account lists & the extension attribute would still work the same. Thanks!