-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathencrypt_or_defer.sh
372 lines (301 loc) · 15.2 KB
/
encrypt_or_defer.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
#!/bin/bash
########################## FILE PATHS AND IDENTIFIERS #########################
# Path to a plist file that is used to store settings locally. Omit ".plist"
# extension.
PLIST="/Library/Preferences/com.bearzooka.encrypt_or_defer"
# (Optional) Path to a logo that will be used in messaging. Recommend 512px,
# PNG format. If no logo is provided, the App Store icon will be used.
LOGO="/private/MyLogo.jpg"
# The identifier of the LaunchDaemon that is used to call this script, which
# should match the file in the payload/Library/LaunchDaemons folder. Omit
# ".plist" extension.
BUNDLE_ID="com.bearzooka.encrypt_or_defer"
################################## MESSAGING ##################################
MSG_ACT_OR_DEFER_HEADING="Your machine needs to be encrypted."
MSG_ACT_OR_DEFER="FileVault Encryption is mandatory and enabling it requieres a reboot.
{{If now is not a good time, you may defer this message until later. }}%DEFER_HOURS% hours remain until your Mac restarts automatically to initiate encryption. This may result in losing unsaved work, so please don't wait until then.
If you have any questions, please create a ticket in HPSM to the CORP INFRA-WP WW STANDARD WORKPLACE MAC OS (APPLE) queue."
# The message users will receive after the deferral deadline has been reached.
MSG_RESTART_HEADING="Please restart now"
MSG_RESTART="Please save your work immediately, then choose Restart from the Apple menu."
#################################### TIMING ###################################
# Number of seconds between the first script run and the updates being forced.
MAX_DEFERRAL_TIME=$(( 60 * 60 * 24 * 3 )) # (259200 = 3 days)
# When the user clicks "Defer" the next prompt is delayed by this much time.
EACH_DEFER=$(( 60 * 60 * 4 )) # (14400 = 4 hours)
# The number of seconds to wait between displaying the "please restart" message
# and attempting a soft restart.
SOFT_RESTART_DELAY=$(( 60 * 10 )) # (600 = 10 minutes)
# The number of seconds to wait between attempting a soft restart and forcing a
# restart.
HARD_RESTART_DELAY=$(( 60 * 5 )) # (300 = 5 minutes)
################################## FUNCTIONS ##################################
convertsecs() {
((h=${1}/3600))
((m=(${1}%3600)/60))
((s=${1}%60))
printf "%02dh:%02dm:%02ds\n" $h $m $s
}
# onscreen message instructing the user to restart.
display_please_restart_msg() {
# Create a jamfHelper script that will be called by a LaunchDaemon.
cat << EOF > "/private/tmp/$HELPER_SCRIPT"
#!/bin/bash
"$jamfHelper" -windowType "utility" -windowPosition "ur" -icon "$LOGO" -title "$MSG_RESTART_HEADING" -description "$MSG_RESTART"
EOF
chmod +x "/private/tmp/$HELPER_SCRIPT"
# Create the LaunchDaemon that we'll use to show the persistent jamfHelper
# messages.
cat << EOF > "/private/tmp/${BUNDLE_ID}_helper.plist"
<?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>KeepAlive</key>
<true/>
<key>Label</key>
<string>${BUNDLE_ID}_helper</string>
<key>Program</key>
<string>/private/tmp/$HELPER_SCRIPT</string>
<key>ThrottleInterval</key>
<integer>10</integer>
</dict>
</plist>
EOF
# Load the LaunchDaemon to show the jamfHelper message.
echo "Displaying \"please restart\" message..."
killall jamfHelper 2>/dev/null
launchctl load -w "/private/tmp/${BUNDLE_ID}_helper.plist"
# After specified delay, attempt a soft restart.
echo "Waiting $(( SOFT_RESTART_DELAY / 60 )) minutes before attempting a \"soft restart\"..."
sleep "$SOFT_RESTART_DELAY"
echo "$(( SOFT_RESTART_DELAY / 60 )) minutes have elapsed since user was prompted to restart. Attempting \"soft\" restart..."
trigger_encrypt_and_restart
}
trigger_encrypt_and_restart() {
#Confirm FileVault is Off
fvStatus=$(sudo fdesetup status)
# If FileVault is already on or in process of encryption, bailout
if [[ "$fvStatus" = *"FileVault is On."* ]] || [[ "$fvStatus" = *"in progress"* ]]; then
echo "Encryption already happening."
clean_up_before_restart
echo "Running jamf recon..."
$jamf recon
echo "Unloading $BUNDLE_ID LaunchDaemon. Script will end here."
launchctl unload -w "/private/tmp/$BUNDLE_ID.plist"
exit 0
fi
echo "Configuring encryption before restart…"
$jamf -event encryptMe
echo "Encryption policy executed, restart ready."
trigger_restart
}
# This function immediately attempts a "soft" restart, waits a specified amount
# of time, and then forces a "hard" restart.
trigger_restart() {
# Immediately attempt a "soft" restart.
CURRENT_USER=$(/usr/bin/stat -f%Su /dev/console)
USER_ID=$(id -u "$CURRENT_USER")
if [[ "$OS_MAJOR" -eq 10 && "$OS_MINOR" -le 9 ]]; then
LOGINWINDOW_PID=$(pgrep -x -u "$USER_ID" loginwindow)
launchctl bsexec "$LOGINWINDOW_PID" osascript -e 'tell application "System Events" to restart'
elif [[ "$OS_MAJOR" -eq 10 && "$OS_MINOR" -gt 9 ]]; then
launchctl asuser "$USER_ID" osascript -e 'tell application "System Events" to restart'
fi
# After specified delay, kill all apps forcibly, which clears the way for
# an unobstructed restart.
echo "Waiting $(( HARD_RESTART_DELAY / 60 )) minutes before forcing a \"hard restart\"..."
sleep "$HARD_RESTART_DELAY"
echo "$(( HARD_RESTART_DELAY / 60 )) minutes have elapsed since user was prompted to restart. Forcing \"hard\" restart..."
USER_PIDS=$(pgrep -u "$USER_ID")
LOGINWINDOW_PID=$(pgrep -x -u "$USER_ID" loginwindow)
for PID in $USER_PIDS; do
# Kill all processes except the loginwindow process.
if [[ "$PID" -ne "$LOGINWINDOW_PID" ]]; then
kill -9 "$PID"
fi
done
if [[ "$OS_MAJOR" -eq 10 && "$OS_MINOR" -le 9 ]]; then
launchctl bsexec "$LOGINWINDOW_PID" osascript -e 'tell application "System Events" to restart'
elif [[ "$OS_MAJOR" -eq 10 && "$OS_MINOR" -gt 9 ]]; then
launchctl asuser "$USER_ID" osascript -e 'tell application "System Events" to restart'
fi
# Mac should restart now, ending this script and encrypting
}
# Clean up plist values and self destruct LaunchDaemon and script.
clean_up_before_restart() {
echo "Cleaning up stored plist values..."
defaults delete "$PLIST" EncryptionForcedAfter 2>/dev/null
defaults delete "$PLIST" EncryptionDeferredUntil 2>/dev/null
echo "Cleaning up main script and LaunchDaemon..."
mv "/Library/LaunchDaemons/$BUNDLE_ID.plist" "/private/tmp/$BUNDLE_ID.plist"
mv "$0" "/private/tmp/"
}
######################## VALIDATION AND ERROR CHECKING ########################
# Copy all output to the system log for diagnostic purposes.
#exec 1> >(logger -s -t "$(basename "$0")") 2>&1
echo "Starting $(basename "$0") script. Performing validation and error checking..."
# Filename we will use for the auto-generated helper script.
HELPER_SCRIPT="$(basename "$0" | sed "s/.sh$//g")_helper.sh"
# Flag variable for catching show-stopping errors.
BAILOUT=false
# Bail out if the jamfHelper doesn't exist.
jamfHelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
if [[ ! -x "$jamfHelper" ]]; then
echo "[ERROR] The jamfHelper binary must be present in order to run this script."
BAILOUT=true
fi
# Bail out if the jamf binary doesn't exist.
PATH="/usr/sbin:/usr/local/bin:$PATH"
jamf=$(which jamf)
if [[ -z $jamf ]]; then
echo "[ERROR] The jamf binary could not be found."
BAILOUT=true
fi
# Bail out if PListBuddy doesn't exist.
PListBuddy="/usr/libexec/PListBuddy"
if [[ ! -x $PListBuddy ]]; then
echo "[ERROR] PListBuddy could not be found."
BAILOUT=true
fi
# If the machine is already encrypted or in process, remove LaunchDaemon and script and stop.
fvStatus=$(sudo fdesetup status)
if [[ "$fvStatus" = *"FileVault is On."* ]] || [[ "$fvStatus" = *"in progress"* ]]; then
echo "[NOT NEEDED] Encryption has already been setup."
clean_up_before_restart
$jamf recon
exit 0
fi
# Determine OS X version.
OS_MAJOR=$(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}')
OS_MINOR=$(/usr/bin/sw_vers -productVersion | awk -F . '{print $2}')
# If the OS is not 10.8 through 10.13, this script may not work. When new
# versions of macOS are released, this logic should be updated after the script
# has been tested successfully.
if [[ "$OS_MAJOR" -eq 10 && "$OS_MINOR" -lt 8 ]] || [[ "$OS_MAJOR" -lt 10 ]]; then
echo "[ERROR] This script requires at least OS X 10.8. This Mac has $OS_MAJOR.$OS_MINOR."
BAILOUT=true
elif [[ "$OS_MAJOR" -eq 10 && "$OS_MINOR" -gt 13 ]] || [[ "$OS_MAJOR" -gt 10 ]]; then
echo "[ERROR] This script has been tested through 10.13 only. This Mac has $OS_MAJOR.$OS_MINOR."
BAILOUT=true
fi
# If any of the errors above are present, bail out of the script now.
if [[ "$BAILOUT" == "true" ]]; then
START_INTERVAL=$(defaults read /Library/LaunchDaemons/$BUNDLE_ID.plist StartInterval 2>/dev/null)
if [[ $? -eq 0 ]]; then
echo "Stopping due to errors, but will try again in $(convertsecs "$START_INTERVAL")."
else
echo "Stopping due to errors."
fi
exit 1
fi
################################ MAIN PROCESS #################################
echo "Validation and error checking passed. Starting main process..."
# Perform first run tasks, including calculating deadline and clearing cache.
FORCE_DATE=$(defaults read "$PLIST" EncryptionForcedAfter 2>/dev/null)
if [[ -z $FORCE_DATE || $FORCE_DATE -gt $(( $(date +%s) + MAX_DEFERRAL_TIME )) ]]; then
FORCE_DATE=$(( $(date +%s) + MAX_DEFERRAL_TIME ))
defaults write "$PLIST" EncryptionForcedAfter -int $FORCE_DATE
fi
# Calculate how much time remains until deferral deadline.
DEFER_TIME_LEFT=$(( FORCE_DATE - $(date +%s) ))
echo "Deferral deadline: $(date -jf "%s" "+%Y-%m-%d %H:%M:%S" "$FORCE_DATE")"
echo "Time remaining: $(convertsecs $DEFER_TIME_LEFT)"
# Get the "deferred until" timestamp, if one exists.
DEFERRED_UNTIL=$(defaults read "$PLIST" EncryptionDeferredUntil 2>/dev/null)
if [[ -n "$DEFERRED_UNTIL" ]] && (( DEFERRED_UNTIL > $(date +%s) && FORCE_DATE > DEFERRED_UNTIL )); then
# If the policy ran recently and was deferred, we need to respect that
# "defer until" timestamp, as long as it is earlier than the deferral
# deadline.
echo "The next prompt is deferred until after $(date -jf "%s" "+%Y-%m-%d %H:%M:%S" "$DEFERRED_UNTIL")."
exit 0
fi
# Make a note of the time before displaying the prompt.
PROMPT_START=$(date +%s)
# If defer time remains, display the prompt. If not, encrypt and restart.
if (( DEFER_TIME_LEFT > 0 )); then
# Substitute the correct number of hours remaining.
if (( DEFER_TIME_LEFT > 7200 )); then
MSG_ACT_OR_DEFER="${MSG_ACT_OR_DEFER//%DEFER_HOURS%/$(( DEFER_TIME_LEFT / 3600 ))}"
MSG_ACT_OR_DEFER="${MSG_ACT_OR_DEFER// 1 hours/ 1 hour}"
elif (( DEFER_TIME_LEFT > 60 )); then
MSG_ACT_OR_DEFER="${MSG_ACT_OR_DEFER//%DEFER_HOURS% hours/$(( DEFER_TIME_LEFT / 60 )) minutes}"
MSG_ACT_OR_DEFER="${MSG_ACT_OR_DEFER// 1 minutes/ 1 minute}"
else
MSG_ACT_OR_DEFER="${MSG_ACT_OR_DEFER//after %DEFER_HOURS% hours/very soon}"
fi
# Determine whether to include the "you may defer" wording.
if (( EACH_DEFER > DEFER_TIME_LEFT )); then
# Remove "{{" and "}}" including all the text between.
MSG_ACT_OR_DEFER="$(echo "$MSG_ACT_OR_DEFER" | sed 's/{{.*}}//g')"
else
# Just remove "{{" and "}}" but leave the text between.
MSG_ACT_OR_DEFER="$(echo "$MSG_ACT_OR_DEFER" | sed 's/[{{|}}]//g')"
fi
# Show the encrypt/defer prompt.
echo "Prompting to encrypt now or defer..."
PROMPT=$("$jamfHelper" -windowType "utility" -windowPosition "ur" -icon "$LOGO" -title "$MSG_ACT_OR_DEFER_HEADING" -description "$MSG_ACT_OR_DEFER" -button1 "Restart Now" -button2 "Defer" -defaultButton 2 -timeout 3600 -startlaunchd 2>/dev/null)
JAMFHELPER_PID=$!
# Make a note of the amount of time the prompt was shown onscreen.
PROMPT_END=$(date +%s)
PROMPT_ELAPSED_SEC=$(( PROMPT_END - PROMPT_START ))
# Generate a duration string that will be used in log output.
if [[ -n $PROMPT_ELAPSED_SEC && $PROMPT_ELAPSED_SEC -eq 0 ]]; then
PROMPT_ELAPSED_STR="immediately"
elif [[ -n $PROMPT_ELAPSED_SEC ]]; then
PROMPT_ELAPSED_STR="after $(convertsecs "$PROMPT_ELAPSED_SEC")"
elif [[ -z $PROMPT_ELAPSED_SEC ]]; then
PROMPT_ELAPSED_STR="after an unknown amount of time"
echo "[WARNING] Unable to determine elapsed time between prompt and action."
fi
# For reference, here is a list of the possible jamfHelper return codes:
# https://gist.github.com/homebysix/18c1a07a284089e7f279#file-jamfhelper_help-txt-L72-L84
# Take action based on the return code of the jamfHelper.
if [[ -n $PROMPT && $PROMPT_ELAPSED_SEC -eq 0 ]]; then
# Kill the jamfHelper prompt.
kill -9 $JAMFHELPER_PID
echo "[ERROR] jamfHelper returned code $PROMPT $PROMPT_ELAPSED_STR. It's unlikely that the user responded that quickly."
exit 1
elif [[ -n $PROMPT && $DEFER_TIME_LEFT -gt 0 && $PROMPT -eq 0 ]]; then
echo "User clicked Restart Now $PROMPT_ELAPSED_STR."
defaults delete "$PLIST" EncryptionDeferredUntil 2>/dev/null
clean_up_before_restart
trigger_encrypt_and_restart
elif [[ -n $PROMPT && $DEFER_TIME_LEFT -gt 0 && $PROMPT -eq 1 ]]; then
# Kill the jamfHelper prompt.
kill -9 $JAMFHELPER_PID
echo "[ERROR] jamfHelper was not able to launch $PROMPT_ELAPSED_STR."
exit 1
elif [[ -n $PROMPT && $DEFER_TIME_LEFT -gt 0 && $PROMPT -eq 2 ]]; then
echo "User clicked Defer $PROMPT_ELAPSED_STR."
NEXT_PROMPT=$(( $(date +%s) + EACH_DEFER ))
defaults write "$PLIST" EncryptionDeferredUntil -int "$NEXT_PROMPT"
echo "Next prompt will appear after $(date -jf "%s" "+%Y-%m-%d %H:%M:%S" "$NEXT_PROMPT")."
elif [[ -n $PROMPT && $DEFER_TIME_LEFT -gt 0 && $PROMPT -eq 239 ]]; then
echo "User deferred by exiting jamfHelper $PROMPT_ELAPSED_STR."
NEXT_PROMPT=$(( $(date +%s) + EACH_DEFER ))
defaults write "$PLIST" EncryptionDeferredUntil -int "$NEXT_PROMPT"
echo "Next prompt will appear after $(date -jf "%s" "+%Y-%m-%d %H:%M:%S" "$NEXT_PROMPT")."
elif [[ -n $PROMPT && $DEFER_TIME_LEFT -gt 0 && $PROMPT -gt 2 ]]; then
# Kill the jamfHelper prompt.
kill -9 $JAMFHELPER_PID
echo "[ERROR] jamfHelper produced an unexpected value (code $PROMPT) $PROMPT_ELAPSED_STR."
exit 1
elif [[ -z $PROMPT ]]; then # $PROMPT is not defined
# Kill the jamfHelper prompt.
kill -9 $JAMFHELPER_PID
echo "[ERROR] jamfHelper returned no value $PROMPT_ELAPSED_STR. Restart Now/Defer response was not captured. This may be because the user logged out without clicking Restart Now/Defer."
exit 1
else
# Kill the jamfHelper prompt.
kill -9 $JAMFHELPER_PID
echo "[ERROR] Something went wrong. Check the jamfHelper return code ($PROMPT) and prompt elapsed seconds ($PROMPT_ELAPSED_SEC) for further information."
exit 1
fi
else
# If no deferral time remains, force encryption now.
echo "No deferral time remains."
clean_up_before_restart
display_please_restart_msg
fi
exit 0