Skip to content

Instantly share code, notes, and snippets.

@Allistah
Forked from lucascantor/applyPasswordPolicy.sh
Last active December 13, 2021 06:36
Show Gist options
  • Save Allistah/c3b1fead3ab7c13c2526abf26ea79978 to your computer and use it in GitHub Desktop.
Save Allistah/c3b1fead3ab7c13c2526abf26ea79978 to your computer and use it in GitHub Desktop.
Generate and apply a standalone macOS password policy, exempting a specified local admin
#!/bin/sh
#########################################################################################
## Creates a local password policy on a Mac running OS 10 or 11.
##
## Created from various files I found around the internet and man pages
## for pwpolicy. I've added the ability to edit the default password
## length which was difficult to find. I attempted to preserve original author names.
##
## This script builds a password policy in a .plist file, imports that
## policy from the file, then deletes that file.
##
## Use "sudo pwpolicy -u <user> -getaccountpolicies"
## to see it, and "sudo pwpolicy -u <user> -clearaccountpolicies" to clear it.
##
## Tested on: OS X 10.10, 10.11, 10.12, and 11.5
##
## Authors:
## Danny Friedman, Civis Analytics IT Manager, CCA, civisanalytics.com
## Jeff Holland, Civis Analytics Sr. Security Engineer, CISSP/GCUX, civisanalytics.com
## Jaime Pirnie, Director of Information Systems
##
## Version 1.x - Initial releases, unknown number of iterations
## Version 2.0 - Jaime Pirnie - Added logic to only include policies needed
## Added ability to modify default password length
## Organized options with mandatory and opotional settings
##
#########################################################################################
######################################################
# 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
#
# Mandatory settings
COMPANY_NAME="mycompany.com" # Company name
MIN_LENGTH=12 # at least x chars for password
MAX_FAILED=5 # max failed logins before locking
LOCKOUT_SECONDS=600 # seconds lockout
PW_EXPIRE=180 # days password expiration
# Optional Settings, set to 0 to disable
MIN_ALPHA_LOWER=0 # at least x lower case letter in password
MIN_UPPER_ALPHA=0 # at least x upper case letter in password
MIN_SPECIAL_CHAR=0 # at least x special character in password
PW_HISTORY=10 # remember last x passwords
exemptAccount1="<exempt local admin username>" #Exempt account used for management.
#
##############################################################################
###################################################
# create pwpolicy.plist in /private/var/tmp
# Password policy using variables above is:
# Change as necessary in variable flowerbox above
# -------------------------------------------------
echo "
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>policyCategoryAuthentication</key>
<array>
<dict>
<key>policyContent</key>
<string>(policyAttributeFailedAuthentications &lt; policyAttributeMaximumFailedAuthentications) OR (policyAttributeCurrentTime &gt; (policyAttributeLastFailedAuthenticationTime + autoEnableInSeconds))</string>
<key>policyIdentifier</key>
<string>ProfilePayload:d79bf40f-9771-4a35-a568-15833429b9e7:minutesUntilFailedLoginReset</string>
<key>policyParameters</key>
<dict>
<key>autoEnableInSeconds</key>
<integer>$LOCKOUT_SECONDS</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>ProfilePayload:d79bf40f-9771-4a35-a568-15833429b9e7:maxPINAgeInDays</string>
<key>policyParameters</key>
<dict>
<key>policyAttributeExpiresEveryNDays</key>
<integer>$PW_EXPIRE</integer>
</dict>
</dict>
<dict>
<key>policyContent</key>
<string>(policyAttributeLastPasswordChangeTime &lt; policyAttributeNewPasswordRequiredTime) and (policyAttributeCurrentTime &gt;= policyAttributeNewPasswordRequiredTime)</string>
<key>policyIdentifier</key>
<string>ProfilePayload:d79bf40f-9771-4a35-a568-15833429b9e7:changeAtNextAuth</string>
<key>policyParameters</key>
<dict>
<key>policyAttributeNewPasswordRequiredTime</key>
<real>1630158879.499939</real>
</dict>
</dict>
</array>
<key>policyCategoryPasswordContent</key>
<array>
<dict>
<key>policyContent</key>
<string>policyAttributePassword matches '.{$MIN_LENGTH,}+'</string>
<key>policyContentDescription</key>
<dict>
<key>en</key>
<string>Enter a password that is $MIN_LENGTH characters or more. (Def)</string>
</dict>
<key>policyIdentifier</key>
<string>com.apple.defaultpasswordpolicy.fde</string>
</dict>
<dict>
<key>policyContent</key>
<string>none policyAttributePasswordHashes in policyAttributePasswordHistory</string>
<key>policyContentDescription</key>
<dict>
<key>en</key>
<string>Not be the same as the previous $PW_HISTORY passwords.</string>
</dict>
<key>policyIdentifier</key>
<string>ProfilePayload:d79bf40f-9771-4a35-a568-15833429b9e7:pinHistory</string>
<key>policyParameters</key>
<dict>
<key>policyAttributePasswordHistoryDepth</key>
<integer>$PW_HISTORY</integer>
</dict>
</dict>" >> /private/var/tmp/pwpolicy.plist
if [ "$MIN_ALPHA_LOWER" != "0" ]; then
echo "
<dict>
<key>policyContent</key>
<string>policyAttributePassword matches '(.*[a-z].*){$MIN_ALPHA_LOWER,}+'</string>
<key>policyIdentifier</key>
<string>At least $MIN_ALPHA_LOWER lower case letter</string>
<key>policyParameters</key>
<dict>
<key>minimumAlphaCharactersLowerCase</key>
<integer>$MIN_ALPHA_LOWER</integer>
</dict>
</dict>" >> /private/var/tmp/pwpolicy.plist
fi
if [ "$MIN_UPPER_ALPHA" != "0" ]; then
echo "
<dict>
<key>policyContent</key>
<string>policyAttributePassword matches '(.*[A-Z].*){$MIN_UPPER_ALPHA,}+'</string>
<key>policyIdentifier</key>
<string>At least $MIN_UPPER_ALPHA upper case letter</string>
<key>policyParameters</key>
<dict>
<key>minimumAlphaCharactersUpperCase</key>
<integer>$MIN_UPPER_ALPHA</integer>
</dict>
</dict>" >> /private/var/tmp/pwpolicy.plist
fi
if [ "$MIN_SPECIAL_CHAR" != "0" ]; then
echo "
<dict>
<key>policyContent</key>
<string>policyAttributePassword matches '(.*[^a-zA-Z0-9].*){$MIN_SPECIAL_CHAR,}+'</string>
<key>policyIdentifier</key>
<string>At least $MIN_SPECIAL_CHAR special character</string>
<key>policyParameters</key>
<dict>
<key>minimumSymbols</key>
<integer>$MIN_SPECIAL_CHAR</integer>
</dict>
</dict>" >> /private/var/tmp/pwpolicy.plist
fi
echo "
</array>
</dict>
</plist>" >> /private/var/tmp/pwpolicy.plist
###################################################
#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 to $LOGGEDINUSER."
echo "Run \"sudo pwpolicy -u $LOGGEDINUSER -getaccountpolicies\" to see it."
echo "Run \"sudo pwpolicy -u $LOGGEDINUSER -clearaccountpolicies\" to remove it."
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment