Skip to main content
Jamf Nation, hosted by Jamf, is a knowledgeable community of Apple-focused admins and Jamf users. Join us in person at the ninth annual Jamf Nation User Conference (JNUC) this November for three days of learning, laughter and IT love.

User password policies on non AD machines

on our devices (10.10.x) we have two local accounts, one "ITadmin" and one "enduser" (also admin level).
We want to set a password policy that only applies to the enduser account as we need to keep the ITadmin accounts the same.

I've tried creating a configuration profile that is set to be user level with only a password payload configured but it doesn't show as applicable to the scoped devices. If I change to computer level it deploys just fine, but is applied to all the accounts.
I've tried using pwpolicy but I can't get the xml file to be in a readable format to be applied.
there is very little help that I can find on how to make a working xml file for this, the man page is very dodgy to say the least and none of the few examples I can find seem to work.
I don't really want to go down the line of using the deprecated command set as I want them to work on 10.11.

Has anyone got a clear example of a working xml file to feed into pwpolicy?

Like Comment
Order by:
SOLVED Posted: by jason.bracy

You could just run a policy with a script payload using a pwpolicy command in the script. the -u option allows you to specify the user account that the pwpolicy applies to. The man page also has an example XML file.

Like
SOLVED Posted: by jholland

The man page for pwpolicy has errors in it. Here's the script we use to deploy a password policy using the correct syntax for pwpollicy. Be sure to edit the variables. Tested and works on 10.10 and 10.11.

#!/bin/sh
##########################################################################################################
## Pupose:  Create a pwpolicy XML file based upon variables and options included below.
##          Policy is applied and then file gets deleted. Use "sudo pwpolicy -u <user> -getaccountpolicies"
##          to see it, and "sudo pwpolicy -u <user> -clearaccountpolicies" to clear it.
## 
## Usage:   Edit variables in Variable flowerbox below.
##          Then run as a policy from Casper, or standalone as root.
##
## Tested on: OS X 10.10 and 10.11
##
## Authors: Danny Friedman, Civis Analytics IT Manager, CCA, civisanalytics.com
##          Jeff Holland, Civis Analytics Sr. Security Engineer, CISSP/GCUX, civisanalytics.com
#########################################################################################################

# get logged-in user and assign it to a variable
LOGGEDINUSER=$(ls -l /dev/console | awk '{print $3}')

echo "LOGGEDINUSER is: $LOGGEDINUSER"

##############################################################################
# Variables for script and commands generated below.
#
# EDIT AS NECESSARY FOR YOUR OWN PASSWORD POLICY
# AND COMPANY INFORMATION
#
COMPANY_NAME="yourcompany.com"  # CHANGE THIS TO YOUR COMPANY NAME
LOCKOUT=300                     # 5min lockout
MAX_FAILED=10                   # 10 max failed logins before locking
PW_EXPIRE=90                    # 90 days password expiration
MIN_LENGTH=8                    # at least 8 chars for password
MIN_NUMERIC=1                   # at least 1 number in password
MIN_ALPHA_LOWER=1               # at least 1 lower case letter in password
MIN_UPPER_ALPHA=1               # at least 1 upper case letter in password
MIN_SPECIAL_CHAR=1              # at least one special character in password
PW_HISTORY=10                   # remember last 10 passwords

exemptAccount1="admin"          #Exempt account used for remote management. CHANGE THIS TO YOUR EXEMPT ACCOUNT
#
##############################################################################

#################################################
##### create pwpolicy.plist in /private/var/tmp
# Password policy using variables above is:
# Change as necessary in variable flowerbox above
# --------------------------------------------------
# pw's must be at least 8 chars
# pw's must have at least 1 lower case letter
# pw's must have at least 1 upper case letter
# pw's must have at least 1 special non-alpha/non-numeric character
# pw's must have at least 1 number
# can't use any of the previous 10 passwords
# pw's expire at 90 days
# 10 failed successive login attempts results in a 300sec lockout, then auto enables

echo "<dict>
 <key>policyCategoryAuthentication</key>
  <array>
   <dict>
    <key>policyContent</key>
     <string>(policyAttributeFailedAuthentications &lt; policyAttributeMaximumFailedAuthentications) OR (policyAttributeCurrentTime &gt; (policyAttributeLastFailedAuthenticationTime + autoEnableInSeconds))</string>
    <key>policyIdentifier</key>
     <string>Authentication Lockout</string>
    <key>policyParameters</key>
  <dict>
  <key>autoEnableInSeconds</key>
   <integer>$LOCKOUT</integer>
   <key>policyAttributeMaximumFailedAuthentications</key>
   <integer>$MAX_FAILED</integer>
  </dict>
 </dict>
 </array>


 <key>policyCategoryPasswordChange</key>
  <array>
   <dict>
    <key>policyContent</key>
     <string>policyAttributeCurrentTime &gt; policyAttributeLastPasswordChangeTime + (policyAttributeExpiresEveryNDays * 24 * 60 * 60)</string>
    <key>policyIdentifier</key>
     <string>Change every $PW_EXPIRE days</string>
    <key>policyParameters</key>
    <dict>
     <key>policyAttributeExpiresEveryNDays</key>
      <integer>$PW_EXPIRE</integer>
    </dict>
   </dict>
  </array>


  <key>policyCategoryPasswordContent</key>
 <array>
  <dict>
   <key>policyContent</key>
    <string>policyAttributePassword matches '.{$MIN_LENGTH,}+'</string>
   <key>policyIdentifier</key>
    <string>Has at least $MIN_LENGTH characters</string>
   <key>policyParameters</key>
   <dict>
    <key>minimumLength</key>
     <integer>$MIN_LENGTH</integer>
   </dict>
  </dict>


  <dict>
   <key>policyContent</key>
    <string>policyAttributePassword matches '(.*[0-9].*){$MIN_NUMERIC,}+'</string>
   <key>policyIdentifier</key>
    <string>Has a number</string>
   <key>policyParameters</key>
   <dict>
   <key>minimumNumericCharacters</key>
    <integer>$MIN_NUMERIC</integer>
   </dict>
  </dict>


  <dict>
   <key>policyContent</key>
    <string>policyAttributePassword matches '(.*[a-z].*){$MIN_ALPHA_LOWER,}+'</string>
   <key>policyIdentifier</key>
    <string>Has a lower case letter</string>
   <key>policyParameters</key>
   <dict>
   <key>minimumAlphaCharactersLowerCase</key>
    <integer>$MIN_ALPHA_LOWER</integer>
   </dict>
  </dict>


  <dict>
   <key>policyContent</key>
    <string>policyAttributePassword matches '(.*[A-Z].*){$MIN_UPPER_ALPHA,}+'</string>
   <key>policyIdentifier</key>
    <string>Has an upper case letter</string>
   <key>policyParameters</key>
   <dict>
   <key>minimumAlphaCharacters</key>
    <integer>$MIN_UPPER_ALPHA</integer>
   </dict>
  </dict>


  <dict>
   <key>policyContent</key>
    <string>policyAttributePassword matches '(.*[^a-zA-Z0-9].*){$MIN_SPECIAL_CHAR,}+'</string>
   <key>policyIdentifier</key>
    <string>Has a special character</string>
   <key>policyParameters</key>
   <dict>
   <key>minimumSymbols</key>
    <integer>$MIN_SPECIAL_CHAR</integer>
   </dict>
  </dict>


  <dict>
   <key>policyContent</key>
    <string>none policyAttributePasswordHashes in policyAttributePasswordHistory</string>
   <key>policyIdentifier</key>
    <string>Does not match any of last $PW_HISTORY passwords</string>
   <key>policyParameters</key>
   <dict>
    <key>policyAttributePasswordHistoryDepth</key>
     <integer>$PW_HISTORY</integer>
   </dict>
  </dict>

 </array>
</dict>" > /private/var/tmp/pwpolicy.plist
##### end of pwpolicy.plist generation script
###################################################

#Check for non-admin account before deploying policy
if [ "$LOGGEDINUSER" != "$exemptAccount1" ]; then
  chown $LOGGEDINUSER:staff /private/var/tmp/pwpolicy.plist
  chmod 644 /private/var/tmp/pwpolicy.plist

  # clear account policy before loading a new one
  pwpolicy -u $LOGGEDINUSER -clearaccountpolicies 
  pwpolicy -u $LOGGEDINUSER -setaccountpolicies /private/var/tmp/pwpolicy.plist

elif [ "$LOGGEDINUSER" == "$exemptAccount1" ]; then
    echo "Currently $exemptAccount1 is logged in and the password policy was NOT set. This script can only be run if the standard computer user is logged in."
    rm -f /private/var/tmp/pwpolicy.plist
    exit 1
fi

#delete staged pwploicy.plist
rm -f /private/var/tmp/pwpolicy.plist

echo "Password policy successfully applied. Run \"sudo pwpolicy -u <user> -getaccountpolicies\" to see it."
exit 0
Like
SOLVED Posted: by dfriedman

To add to Jeff's post above – we found the script above to be extremely effective in administering a password policy without LDAP.

In addition to the script above, you can create an Extension Attribute (script) to check if a computer has the password policy. The the Extension Attribute can then be used no only to see if individual computers have your password policy, but you can also create a Smart Group based on the attribute to deploy to computers that do not have your password policy.

We use this extension attribute (using a value of "5" for the number of previous passwords that pwpolicy tracks):

#!/bin/sh
# This script checks if the existing pwpolicy (or non-existent pwpolicy) matches the pwpolicy by looking to see if
# the string <string>Does not match any of last 5 passwords</string> exists in the current computer's pwpolicy

# get logged-in user and assign it to a variable
LOGGEDINUSER=`who -q | head -1 | cut -d ' ' -f1`
if [ "$LOGGEDINUSER" == "_mbsetupuser" ]; then
    LOGGEDINUSER=`who -q | head -1 | cut -d ' ' -f2`
fi

CURRENTPWPOLICY=`pwpolicy -u $LOGGEDINUSER -getaccountpolicies | grep 'Does not match any of last 5 passwords' | sed -e 's/  //g' `
PWPOLICY="<string>Does not match any of last 5 passwords</string>"
if [ "$CURRENTPWPOLICY" == "$PWPOLICY" ]; then
    echo "<result>$LOGGEDINUSER has the password policy</result>"
else
    echo "<result>$LOGGEDINUSER DOES NOT have the password policy</result>"
fi
exit 0

There are other ways to accomplish this, but we have found this one to be a simple and easy way to do it.

Like
SOLVED Posted: by marklamont

thanks for the help @jholland @dfriedman , much appreciated.

Like
SOLVED Posted: by dfriedman

No problem. Glad it did the trick!

Like
SOLVED Posted: by lpadmin

Thank you jholland, that script works great. One question that I have is how would you turn this policy off?

Like
SOLVED Posted: by dfriedman

@lpadmin – you can clear policies for a specific user with

sudo pwpolicy -u <username> -clearaccountpolicies

I am not sure how to clear policies for all users in a single command, as the man page for pwpolicy leaves quite a bit to be desired, but you can do this for every user on a given system to remove the password policy.

Like
SOLVED Posted: by gachowski

@jholland

Thanks Jeff I think that is the 1st public use of the policyCategory.plist, for sure it's the 1st I have found !!!

Thanks
C

PS Do you "guys" have a git repository? : )

Like
SOLVED Posted: by acaveny

Because this happens within the logged in user account, does it affect the KeyChain in anyway or is the KeyChain automatically synced with the new user password?

Like
SOLVED Posted: by jholland

@gachowski I'll have to create a repo for it. Will try and get that up soon.

Like
SOLVED Posted: by dfriedman

@acaveny – pwpolicy does not interact at all with login.keychain. When pwpolicy enforcement triggers the OS to prompt the user to change the password, the login password is automatically synced in the same way that a user manually changing their password would (as it is the standard core OS process controlling the actual password change).

Like
SOLVED Posted: by gachowski

@jholland No rush, you "guys" know what is going on and the more you can share the better : ) : )

C

Like
SOLVED Posted: by dfriedman

@acaveny – Sorry, I realize that I didn't directly answer your question, but tried to imply the answer. The login keychain will sync properly and automatically, and you should not have to perform any additional steps.

@gachowski – We should have the repo up relatively soon. I have a bunch of scripts that I'll be putting up. If you use FileVault 2, I imagine that you will really enjoy these ;)

Like
SOLVED Posted: by gachowski

@dfriedman

SWEET!!!! Thank you!! Thank you!!

C

Like
SOLVED Posted: by lpadmin

I just encountered a weird issue, not sure if it is related to this script. Sorry for being long I just want to give all of the details.

I configured the variables in the script to work for my environment. The biggest change that I made was to remove the sections looking for a minimum amount of special characters. I then pushed this out to 20 test computers and everything was working. I then pushed it to my Computer yesterday afternoon. SInce my password was old it prompted me to change it and I did. I have logged out and signed back in multiple times since changing the password without any issues. (Quick note my computer is using FireVault). This morning I turned on the computer and successfully logged in, I then walked away for a view minutes. When I came back I went to unlock the computer by using my password. The computer would not accept the password and acted like it was the wrong one. After being denied a few times I logged into the admin account just to make sure my Keyboard was not messed up, and was able to. I then restarted the computer and got the prompt to enter a password to unlock the disk, I entered the same new password and it unlocked the disk, at the login screen though it still denied the password. I then logged into the admin account and just the password again from Users & Groups, then I was able to get back into my account.

Has anyone else encountered this, or know what could have caused it?

Like
SOLVED Posted: by lpadmin

Update;

So it locked me out again. This time I turned off FileVault and it allowed me back in after the restart.

Like
SOLVED Posted: by jholland

@lpadmin That hasn't been the case with our user base, which is 100 strong.

You can determine if it's the pw policy by logging in as your admin user and clearing the account policy for the specific user (sudo pwpolicy -u <username> -clearaccountpolicies).

Like
SOLVED Posted: by dfriedman

@lpadmin

That is pretty odd. I did see some issues that now sound similar, relating to FileVault 2. The issue that I experienced was that a user, while seemingly showing up as a FileVault 2 user in the OS X GUI (in System Preferences and the FV2 login screen post-boot), was not in fact a FileVault 2 authenticated user, despite OS X acting like it was.

Thus, the user visibly shows up to unlock the disk after boot (at the FV2 login screen), but no password worked for that user to unlock the disk. The admin user on the computer, that also was an approved FV2 user, was able unlock the disk. The admin could then log out or use fast user switching get to a non-FV2 user login. There, the account that previously wasn't able to unlock the disk was able to log in with the same password that the FV2 authentication did not accept prior.

The fix for this issue was to remove the user in question from FV2 and then re-add the user (via command line). This would imply that whenever the user was forced to change his password, the password did not sync with the FV2 mechanism that allows that password to decrypt/unlock the encrypted disk. Disabling and re-enabling FV2 also resolved this issue, which is a long, arduous process.

This has not been an issue for the many of our machines, but I have dealt with it on about 4 of our 100 managed computers.

Like
SOLVED Posted: by jholland

@lpadmin

Danny and I just talked about this. It seems to be a edge case that Apple didn't take into account, even though only resident OS X tools like pwpolicy are used.

Assume your password is 100 days old, and you apply a policy that says passwords cannot be older than 90 days. You are then immediately dumped into a prompt to change your password. When you do, it seems the password associated with your user in FileVault2 is not updated. Conversely, if you change your password via system preferences, the password associated with FileVault2 is updated.

If filevault2 is not in use, or your password is not older than your expiration time, everything is kosher when using this script.

We get around this particular issue by warning users with some other Casper scripts that tell them "User, your password expires in 7 days, please change it now" using notifications and repeats every 8 hours. We'll try and get that script and a few others up on a GitHub page we're going to build soon.

One solution for your 20 computers is to have them all change their password, then push the policy. The password will not be older than the expiration time so they won't get the prompt. In addition, have them change their password before it expires so they don't prompted to change their password as you did and run into filevault issues.

Hope that helps.

Like
SOLVED Posted: by lpadmin

Thanks for the input jholland & dfriedman.

This issue is definitely being caused by FileVault. As soon as my computer checked in without FileVault enabled and the JSS forced me to re-enable it, the password stopped working. I have turned it back off and removed my computer from the policy. Hopefully that fixes it for now, I will be looking for the scripts that you post on Github. Until then I will just leave FileVault turned off.

Like
SOLVED Posted: by dfriedman

@lpadmin

Ok that sounds good for now. I think it's ridiculous that Apple hasn't resolved this issue, but I suppose the man page for pwpolicy adequately shows Apple's dedication to the tool.

Either Jeff or I will post an update to this posting as soon as our GitHub is up.

Like
SOLVED Posted: by dellis
#!/bin/sh
# This script checks if the existing pwpolicy (or non-existent pwpolicy) matches the pwpolicy by looking to see if
# the string <string>Does not match any of last 5 passwords</string> exists in the current computer's pwpolicy

# get logged-in user and assign it to a variable
LOGGEDINUSER=`who -q | head -1 | cut -d ' ' -f1`
if [ "$LOGGEDINUSER" == "_mbsetupuser" ]; then
    LOGGEDINUSER=`who -q | head -1 | cut -d ' ' -f2`
fi

CURRENTPWPOLICY=`pwpolicy -u $LOGGEDINUSER -getaccountpolicies | grep 'Does not match any of last 5 passwords' | sed -e 's/  //g' `
PWPOLICY="<string>Does not match any of last 5 passwords</string>"
if [ "$CURRENTPWPOLICY" == "$PWPOLICY" ]; then
    echo "<result>$LOGGEDINUSER has the password policy</result>"
else
    echo "<result>$LOGGEDINUSER DOES NOT have the password policy</result>"
fi
exit 0

If I run the extension attribute code above I never get "$LOGGEDINUSER has the password policy"

My result is always:
<result>test DOES NOT have the password policy</result>

Yet the result of:
echo $CURRENTPWPOLICY
echo $PWPOLICY

Yields:
<string>Does not match any of last 10 passwords</string>
<string>Does not match any of last 10 passwords</string>

So $CURRENTPWPOLICY = $PWPOLICY

Yet if at the terminal I type:
pwpolicy -u test -getaccountpolicies | grep 'Does not match any of last 10 passwords' | sed -e 's/ //g'

I get:
<string>Does not match any of last 10 passwords</string>

So I know the script policy (way above) is applied. Somehow CURRENTPWPOLICY is not equaling PWPOLICY. Even though I know we cut&pasted both scripts verbatim, but only changed the 5 to a 10. Even changing everything back to the original 5 in both scripts, does not help.

Thoughts?

Like
SOLVED Posted: by dfriedman

@dellis one possibility is that there is an issue with the $LOGGEDINUSER variable definition.

Most of the time the

$LOGGEDINUSER=`who -q | head -1 | cut -d ' ' -f1`
if [ "$LOGGEDINUSER" == "_mbsetupuser" ]; then
    LOGGEDINUSER=`who -q | head -1 | cut -d ' ' -f2`
fi

works, but I have recently started using LOGGEDINUSER=$( ls -l /dev/console | awk '{print $3}' ) which always returns the correct user. The reason the above command does not always works has to do with the many hidden system users in OS X.

Changing the way that $LOGGEDINUSER is set (in both the script and the Extension Attribute) could do it.

That is one possibility. If that doesn't work, another possibility is that when you copied the script above, it copied the tab incorrectly in the sed -e 's/ //g' expression (the space between the first and second slashes). You could try editing both of the scripts in a text editor (like TextWrangler or Atom), and replace that tab/space character with a tab, resave, upload to JSS, and see if that resolves it.

Like
SOLVED Posted: by dellis

@dfriedman Yep, it was the tab....so easy to miss. Thanks

Like
SOLVED Posted: by dfriedman

@dellis no problem! Glad it worked!

Like
SOLVED Posted: by gachowski

@lpadmin @dfriedman

I am seeing the same issue with some AD accounts a FV2.. In the GUI the accounts added after the 1st user, look like they are FV2 enable and show up at the FV2 log in screen but can't authenticate at the FV2 log in. However they can log in at the OS level...

C

Like
SOLVED Posted: by dfriedman

@gachowski @lpadmin

That's too bad. Unfortunately, this problem isn't easily resolved, as this is an issue with stock Apple tools (pwpolicy, FileVault 2), and how they interact. Removing and re-adding that user as a File Vault 2 user will fix this, but it's ugly (you will also need another existing account to be encrypted with FileVault 2 in order to use the method below).

You can remove the user with

fdesetup remove <username>

Then, re-enable for the user with

fdesetup add <username>

This can be scripted and you can prompt the user for input using the -inputlist functionality. @rtrouton has documented fdesetup very well on his blog.

Like
SOLVED Posted: by evobe

This script seems perfect for what our org is trying to do but everytime I run it I get "error: Credential operation failed because an invalid parameter was provided." I cannot figure out where I'm going wrong. I did change the LOGGEDINUSER to =$( ls -l /dev/console | awk '{print $3}' ) , but I received the same results before and after the change. Can anyone guide me as to what I might be missing here?

Like
SOLVED Posted: by jholland

@evobe What version of OS X are you running this on? This was written for Yosemite/El Cap (10.10/10.11) which deprecated the old pwpolicy commands for the XML formatted plist.

Like
SOLVED Posted: by evobe

@jholland I'm using el capitan (10.11.3), only a few of our machines are on yosemite and we're in the process of upgrading them to El Cap.

Like
SOLVED Posted: by jholland

@evobe Are you running the script as root locally (or using sudo), or are you running from a Casper policy? It should work either way, but if you are running locally you need to run it with privileges.

Like
SOLVED Posted: by evobe

@jholland, I've done it as both, I've run it using a policy with a trigger through JSS and I've run it locally, each time I've received the same error. I put in my password and it just doesn't work. Driving us crazy since it seems to work perfectly for you guys.

Like
SOLVED Posted: by jholland

@evobe Hmm, strange, it should work. Must be something simple:

  • View your current pwpolicy, if any, with this command (edit the user):
(sudo pwpolicy -u <user> -getaccountpolicies)
  • Then clear the pwpolicy with this command (edit the user):
(sudo pwpolicy -u <user> -clearaccountpolicies)
  • Might be a copy paste error? Try copying the script again from this thread, chmod/chown it accordingly, run "file" on the script to make sure it's a "POSIX shell script text executable" file.
Like
SOLVED Posted: by rhooper

@jholland I just ran this script to be pushed out to two devices, even though the JSS says they completed it only one actually asked for a password change.
I did have to comment out some of the rules, but it seemed to work.
Flushed out the logs and reran the script, it says it runs, but still nothing runs:
The following Log is noted:
Executing Policy Password reset script
Running script Password reset script...
Script exit code: 0
Script result: LOGGEDINUSER is: teacher<br/>Clearing account policies for user <teacher><br/>Error: The data is not in the correct format.<br/>Password policy successfully applied. Run "sudo pwpolicy -u <user> -getaccountpolicies" to see it.<br/>

The OS is 10.12.5
We may need to run this script a few times a year or on an as needed basis. Is this going to cause problems?
Any help is greatly appreciated.

Like
SOLVED Posted: by gachowski

FYI,

Parts of this are broken in High Sierra when using FV2... please test and open feedback with Apple if you have access..

C

Like
SOLVED Posted: by Chumachos

@lpadmin

Thanks for the input jholland & dfriedman. This issue is definitely being caused by FileVault. As soon as my computer checked in without FileVault enabled and the JSS forced me to re-enable it, the password stopped working. I have turned it back off and removed my computer from the policy. Hopefully that fixes it for now, I will be looking for the scripts that you post on Github. Until then I will just leave FileVault turned off.

There are any news about this issue? I have the same problem in the macs of my enterprise

Like
SOLVED Posted: by Rmerritt

This script is great for enforcing password complexity on the next password change. Is there a way to for this to also expire the user's existing password?

I am using this as part of an imaging process via deploy studio.

Like
SOLVED Posted: by jason.bracy

There is another policy in pwpolicy called "newPasswordRequired":

newPasswordRequired          If 1, the user will be prompted for a new
                             password at the next authentication. Appli-
                             cations that do not support change password
                             will not authenticate.

Not sure how you would apply it in your environment, but that is the command.

Like
SOLVED Posted: by Rmerritt

@jason.bracy 100% correct. Within a few moments of posting I found the the command and entered it below the set new policies section

-setpolicy "newPasswordRequired=1" being added resolved this issue.

Like
SOLVED Posted: by bethanyscott87

Does anyone have something like this that works for 10.12 and 10.13 machines?

Like
SOLVED Posted: by jason.bracy

I haven't tested with 10.13, but the pwpolicy script works with 10.12

Like
SOLVED Posted: by gachowski

I was using parts of the code for parts of my passwords rules in 10.12...

C

Like
SOLVED Posted: by mani2care

@jholland posted script helped me.
but this is applicable for one user who logs in for the first user on the machine, this will not impact with not an exemptAccount1
when moving forward exemptAccount1 creates a new account this script not working. and password policy not applied.

do you have any new script for this issue to get rectified?

Like