├── .gitignore ├── efi_extension_attribute.sh ├── README.md ├── clear_efi_pw_1.2.sh └── set_EFI_pw_1.4.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /efi_extension_attribute.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #============================ 3 | # efi_extension_attribute.sh 4 | # jeremy gray, Omada Health, 2019 5 | # 6 | # a script to capture an EFI password to Jamf Extension Attribute. 7 | # 8 | # disclaimer: i'm providing these scripts as-is, and don't have bandwidth to offer support or updates. 9 | #============================ 10 | 11 | if [ -d "/Users/administrator/Desktop" ]; then 12 | pwFile="/Users/administrator/Desktop/pwFile.txt" 13 | elif [ -d "/private/var/administrator/Desktop" ]; then 14 | pwFile="/private/var/administrator/Desktop/pwFile.txt" 15 | else 16 | echo "Could not find administrator account." 17 | exit 1 18 | fi 19 | 20 | result="$(cat "$pwFile")" 21 | 22 | echo "$result" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EFI Deployment, Rotation, Clearing, and Documentation 2 | ### A mechanism to set, clear, and manage EFI passwords for Mac via Jamf 3 | 4 | ## The Puzzle: 5 | We want to deploy Macs with EFI passwords set to prevent users from booting to Target Disk Mode or to other boot volumes. Jamf provides a uniform EFI password mechanism, but we don't want to give this out to our end users, as they would now know the single password for all of our machines. 6 | 7 | Instead, we want to give all machines their own unique EFI passwords that are known to IT but unknown to the end user. When delivering support, especially remotely, we want to provide the end user their own EFI password as needed, and then have the ability to rotate the EFI password to a new one on demand. 8 | 9 | ## The Solution: 10 | I hacked together an `expect` script to interact with Apple's `firmwarepasswd` utility, wrapped in Bash. 11 | 12 | On first run during deployment, `set_EFI_pw_1.4.sh` generates a new password, sets it, writes it to a file, `chmod 600` the file (root visible only), and echoes the new password to the Jamf policy log (this allows us password history in case something goes wrong). 13 | 14 | On subsequent runs, the script will look in the Jamf policy (in $4) for any default EFI password (in our example, the old single EFI password) and for the EFI password that was set during the most recent run. It verifies one of those passwords is correct, then generates and sets a new one. 15 | 16 | Once set, the script runs a `jamf recon` to allow `efi_extension_attribute.sh` to read and catch the EFI password to display it in the JSS. 17 | 18 | Finally, when a machine needs to be wiped and its record removed from the JSS, we clear the EFI password using `clear_efi_pw_1.2.sh`. We fire the script using a Self Service policy scoped to all machines, but limited to IT team members. IT must log into Self Service to make the policy visible. 19 | 20 | Here it is all together: 21 | - Put `efi_extension_attribute.sh` into your JSS at Settings > Computer Management > Extension Attributes, scoped to all. 22 | - Run `set_EFI_pw_1.4.sh` during provisioning with a restart payload in the policy (we do this in our PreStage Enrollments). 23 | - Put `set_EFI_pw_1.4.sh` with a restart payload into a Self Service policy scoped to all. Users can run this as often as they want without any repercussions. 24 | - Put `clear_efi_pw_1.2.sh` with a restart payload into a Self Service, scoped to all, but limited to members of IT only. We do this so that machines ready for de/re-provisioning can be cleared, without allowing end users to remove EFI passwords themselves. 25 | 26 | ## Gotchas 27 | We've noticed that order of operations is important, particularly when clearing a password. The EFI password lives in three places: 28 | - `pwFile.txt` (by default, I'm keeping this at `/Users/administrator/Desktop`) 29 | - JSS Extentension Attribute 30 | - JSS Policy Log 31 | 32 | Keep in mind that both the `set` and `clear` scripts require that `pwFile.txt` exists. If you delete this file or it becomes otherwise lost, the scripts will fail. 33 | Also, the next time `jamf recon` runs, it will see no `pwFile.txt` and drop the password out of the Extension Attribute. 34 | And if you remove the machine from the JSS, you will lose policy history and the Extension Attribute containing the active password. 35 | If you lose all three, you'll need Apple to clear the EFI password however they do that. 36 | Be careful. 37 | 38 | ## Reuse 39 | This was a fun and useful project, so I'm offering it to the community to play with and deploy as you see fit. I'm not in a position to offer any ongoing support, and may never update this again. Please feel free to fork and mutate as you wish. Use at your own risk. 40 | -------------------------------------------------------------------------------- /clear_efi_pw_1.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #============================ 3 | # clear_EFI_pw_1.2.sh 4 | # jeremy gray, Omada Health, 2019 5 | # 6 | # a script to remove a known EFI password. this will check for a default password set in $4 of the Jamf policy, a password stored in pwFile.txt, and a password you provide at a prompt. 7 | # 8 | # all three options are tested, and if one is verified, it will be cleared. 9 | # 10 | # disclaimer: i'm providing these scripts as-is, and don't have bandwidth to offer support or updates. 11 | #============================ 12 | 13 | #--------- Exit codes 14 | # 1: $4 is not set 15 | # 2: administrator user may not exist 16 | # 3: could not set pwFile path 17 | # 5: could not verify existing pw 18 | # 6: generic error 19 | 20 | #--------- Declare some vars 21 | logfile="/Library/Logs/[YourOrgHere]/JamfEFI.log" 22 | EFIVerified="" 23 | EFIisSet=0 24 | 25 | #--------- Log function 26 | function log () { 27 | echo $(date) "|" $1 $2 >> $logfile 28 | } 29 | 30 | log "---------- Starting EFI clear..." 31 | 32 | #--------- Check for $4 33 | if [ -z "$4" ]; then 34 | log 'Please set a value for $4 and try again.' 35 | exit 1 36 | fi 37 | 38 | #--------- Check for admin user 39 | if [ ! -d "/Users/administrator" ] && [ ! -d "/private/var/administrator" ]; then 40 | log 'Please verify the user "administrator" exists and try again.' 41 | exit 2 42 | fi 43 | 44 | #--------- Set pwFile path 45 | if [ -d "/Users/administrator" ]; then 46 | pwFile="/Users/administrator/Desktop/pwFile.txt" 47 | elif [ -d "/private/var/administrator" ]; then 48 | pwFile="/private/var/administrator/Desktop/pwFile.txt" 49 | else 50 | log "Could not set path to pwFile." 51 | exit 3 52 | fi 53 | 54 | #--------- Get arbitrary old password 55 | oldEFI_prompted=$(/usr/bin/osascript -e 'set oldEFI_prompted to the text returned of (display dialog "Please enter a passphrase to use this script." default answer "" with icon stop buttons {"Cancel", "Continue"} default button "Continue" with hidden answer)') 56 | 57 | #--------- Check for EFI set 58 | e="$(firmwarepasswd -check | cut -c 19)" 59 | if [ $e == "Y" ]; then 60 | EFIisSet=1 61 | log "EFI password is set" 62 | fi 63 | 64 | export exp_EFIisSet=${EFIisSet} 65 | 66 | #--------- Gather old EFI candidates 67 | declare -a candidates 68 | 69 | candidates+=("$oldEFI_prompted") 70 | candidates+=("$(cat "$pwFile")") 71 | candidates+=("$4") 72 | 73 | log "Old EFI passwords" "${candidates[@]}" 74 | 75 | #--------- Prepare candidates for expect block 76 | export exp_oldEFI=${candidates[@]} 77 | 78 | #--------- MAIN 79 | #--------- Verify we know current EFI pw 80 | EFIVerified="$(expect -d <<'Done' 81 | log_user 0 82 | set result "Failed"; 83 | set correct "--"; 84 | 85 | for {set x 0} {$x < 3} {incr x 1} { 86 | set try [lindex $env(exp_oldEFI) $x] 87 | 88 | spawn firmwarepasswd -verify 89 | 90 | expect "Enter password:" 91 | send -- $try 92 | send -- "\r" 93 | 94 | expect { 95 | "Correct" { 96 | set result "Success"; 97 | set correct "$try"; 98 | } 99 | } 100 | } 101 | 102 | log_user 1 103 | 104 | puts $correct; 105 | Done 106 | )" 107 | 108 | log "Verified EFI" "$EFIVerified" 109 | 110 | function setAndReport () { 111 | success="$(clearEFI)" 112 | if [ "$success" = "Success" ]; then 113 | rm $pwFile 114 | log "It worked. OMG. EFI password is cleared." 115 | jamf recon 116 | exit 0 117 | else 118 | log "Bummer. No dice. EFI password is still set." 119 | exit 6 120 | fi 121 | } 122 | 123 | #--------- Clear EFI 124 | function clearEFI () { 125 | export exp_EFIVerified=$EFIVerified 126 | expect -d <<'Done' 127 | log_user 0 128 | spawn firmwarepasswd -delete 129 | set result "" 130 | 131 | if { $env(exp_EFIisSet) == 1 } { 132 | expect "Enter password:" 133 | send -- $env(exp_EFIVerified) 134 | send -- "\r" 135 | } 136 | 137 | expect { 138 | "Password incorrect" { 139 | set result "Failed" 140 | } -re ".*(Password removed.*)" { 141 | set result "Success" 142 | } 143 | } 144 | 145 | log_user 1 146 | puts $result 147 | Done 148 | } 149 | 150 | #--------- EFI was off 151 | if [ $EFIisSet -eq 0 ]; then 152 | log "EFI password was already disabled" 153 | exit 0 154 | fi 155 | 156 | #--------- EFI was already set and we know the old pw 157 | if [ $EFIisSet -eq 1 ] && [ "$EFIVerified" != "--" ]; then 158 | setAndReport 159 | fi 160 | 161 | #--------- EFI was already set and we do not know the old pw 162 | if [ "$EFIVerified" = "--" ]; then 163 | log "Could not verify the existing password." 164 | exit 5 165 | fi 166 | 167 | #--------- Generic error 168 | log "Something else went wrong." 169 | exit 6 -------------------------------------------------------------------------------- /set_EFI_pw_1.4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #============================ 3 | # set_EFI_pw_1.4.sh 4 | # jeremy gray, Omada Health, 2019 5 | # 6 | # a script to set a new, random EFI password. this will check for a default password set in $4 of the Jamf policy and a password stored in pwFile.txt. 7 | # 8 | # both options are tested, and if one is verified, or if no EFI password is set, will generate a 10-character alphanumeric password, save it to a pwFile readable only by root, and set EFI password to it. 9 | # 10 | # use this script during deployment and also in Self Service to allow for arbitrary EFI rotation as needed. 11 | # 12 | # bonus points: create a Jamf Extension Attribute using efi_extension_attribute.sh to collect EFIs into your JSS. 13 | # 14 | # disclaimer: i'm providing these scripts as-is, and don't have bandwidth to offer support or updates. 15 | #============================ 16 | 17 | #--------- Exit codes 18 | # 1: $4 is not set 19 | # 2: administrator user may not exist 20 | # 3: could not set pwFile path 21 | # 4: could not change password 22 | # 5: could not verify existing pw 23 | # 6: generic error 24 | 25 | #--------- Declare some vars 26 | EFIVerified="" 27 | newEFIpw="" 28 | EFIisSet=0 29 | logfile="/Library/Logs/[YourOrgHere]/JamfEFI.log" 30 | 31 | #--------- Log function 32 | function log () { 33 | echo $(date) "|" $1 $2 >> $logfile 34 | } 35 | 36 | log "---------- Starting EFI rotation..." 37 | 38 | #--------- Check for $4 39 | if [ -z "$4" ]; then 40 | log 'Please set a value for $4 and try again.' 41 | exit 1 42 | fi 43 | 44 | #--------- Check for admin user 45 | if [ ! -d "/Users/administrator" ] && [ ! -d "/private/var/administrator" ]; then 46 | log 'Please verify the user "administrator" exists and try again.' 47 | exit 2 48 | fi 49 | 50 | #--------- Set pwFile path 51 | if [ -d "/Users/administrator" ]; then 52 | pwFile="/Users/administrator/Desktop/pwFile.txt" 53 | elif [ -d "/private/var/administrator" ]; then 54 | pwFile="/private/var/administrator/Desktop/pwFile.txt" 55 | else 56 | log "Could not set path to pwFile." 57 | exit 3 58 | fi 59 | 60 | #--------- Touch log 61 | touch $logfile 62 | log "Touched logfile at "$logfile 63 | chmod 600 $logfile 64 | log "Set chmod 600 to $logfile" 65 | 66 | #--------- Check for EFI set 67 | e="$(firmwarepasswd -check | cut -c 19)" 68 | if [ $e == "Y" ]; then 69 | EFIisSet=1 70 | log "EFI password is set" 71 | fi 72 | 73 | export exp_EFIisSet=${EFIisSet} 74 | 75 | #--------- Set oldEFIpw 76 | if [ -s "$pwFile" ]; then 77 | oldEFIpw="$(cat "$pwFile")" 78 | elif [ -z $oldEFIpw ]; then 79 | oldEFIpw="$4" 80 | else 81 | log "Something else went wrong." 82 | exit 6 83 | fi 84 | 85 | #--------- Log old EFI 86 | log "Old EFI:" $oldEFIpw 87 | 88 | #--------- Prepare oldEFI for expect block 89 | export exp_oldEFI=${oldEFIpw} 90 | 91 | #--------- Generate new EFI pw to file 92 | function genNewEFI () { 93 | rm "$pwFile" 94 | newEFIpw="$(openssl rand -base64 8 |md5 |head -c10;echo)" 95 | log "New EFI:" $newEFIpw 96 | 97 | touch "$pwFile" 98 | echo "$newEFIpw" > "$pwFile" 99 | chmod 600 "$pwFile" 100 | log "Saved new EFI password to $pwFile and ran chmod 600" 101 | } 102 | 103 | #--------- Set new EFI pw 104 | function setNewEFI () { 105 | expect -d <<'Done' 106 | log_user 0 107 | spawn firmwarepasswd -setpasswd 108 | set result "" 109 | 110 | if { $env(exp_EFIisSet) == 1 } { 111 | expect "Enter password:" 112 | send -- $env(exp_oldEFI) 113 | send -- "\r" 114 | } 115 | 116 | expect "Enter new password:" 117 | send -- $env(exp_newEFI) 118 | send -- "\r" 119 | 120 | expect "Re-enter new password:" 121 | send -- $env(exp_newEFI) 122 | send -- "\r" 123 | 124 | expect { 125 | "Passwords do not match." { 126 | set result "Failed" 127 | } -re ".*(Password changed.*)" { 128 | set result "Success" 129 | } 130 | } 131 | 132 | log_user 1 133 | puts $result 134 | Done 135 | } 136 | 137 | #--------- MAIN 138 | #--------- Verify we know current EFI pw 139 | EFIVerified="$(expect <<'Done' 140 | log_user 0 141 | spawn firmwarepasswd -verify 142 | 143 | expect "Enter password:" 144 | send -- $env(exp_oldEFI)\r 145 | 146 | expect -re {\n(\S+orrect)} 147 | set result $expect_out(1,string) 148 | 149 | log_user 1 150 | puts $result 151 | Done 152 | )" 153 | 154 | if [ "$EFIVerified" = "Incorrect" ]; then 155 | log "Could not verify the existing password." 156 | exit 5 157 | fi 158 | 159 | function setAndReport () { 160 | genNewEFI 161 | export exp_newEFI="${newEFIpw}" 162 | success="$(setNewEFI)" 163 | if [ "$success" = "Success" ]; then 164 | log "It worked. OMG." 165 | log "Now we need to reboot." 166 | echo "New EFI: $exp_newEFI" # do this so new password echoes into Jamf policy log 167 | jamf recon 168 | exit 0 169 | else 170 | log "Bummer. No dice." 171 | exit 6 172 | fi 173 | } 174 | 175 | #--------- EFI was off 176 | if [ $EFIisSet -eq 0 ]; then 177 | setAndReport 178 | fi 179 | 180 | #--------- EFI was already set and we know the old pw 181 | if [ $EFIisSet -eq 1 ] && [ "$EFIVerified" = "Correct" ]; then 182 | setAndReport 183 | fi 184 | 185 | #--------- Generic error 186 | log "Something else went wrong." 187 | exit 6 --------------------------------------------------------------------------------