User password policies on non AD machines

marklamont
Contributor III

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?

2 ACCEPTED SOLUTIONS

jholland
New Contributor III

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

View solution in original post

danny_friedman_
New Contributor III

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.

View solution in original post

44 REPLIES 44

bethanyscott87
New Contributor

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

jason_bracy
Contributor III

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

gachowski
Valued Contributor II

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

C

mani2care
Contributor

@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?

Doc_Jam
New Contributor

Thanks you for this script, I am trying to set it up to use in my setup and it works really well, however does anyone know what the syntax would be if I wanted to exempt more than one user? 

For context, our users are standard users but they have access to an admin account to approve updates, this way I avoid them using admin accounts for day to day usage, without fully taking away their ability to manage their own devices.  I also have an admin account. so each machine will have: 

User - Standard - I want the password to reset
StaffAdmin - Admin - No one every actually logs in with this account but the credentials will be sued to approve updates/install apps - I do NOT want this password to reset
ITAdmin - Admin - me - I dont want this to reset either.