-
Notifications
You must be signed in to change notification settings - Fork 68
/
cm_cdp_cdh_log4j_jndi_removal.sh
executable file
·563 lines (481 loc) · 18.6 KB
/
cm_cdp_cdh_log4j_jndi_removal.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
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
#!/bin/bash
# CLOUDERA SCRIPTS FOR LOG4J
#
# (C) Cloudera, Inc. 2021. All rights reserved.
#
# Applicable Open Source License: Apache License 2.0
#
# CLOUDERA PROVIDES THIS CODE TO YOU WITHOUT WARRANTIES OF ANY KIND. CLOUDERA DISCLAIMS ANY AND ALL EXPRESS AND IMPLIED WARRANTIES WITH RESPECT TO THIS CODE, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. CLOUDERA IS NOT LIABLE TO YOU, AND WILL NOT DEFEND, INDEMNIFY, NOR HOLD YOU HARMLESS FOR ANY CLAIMS ARISING FROM OR RELATED TO THE CODE. ND WITH RESPECT TO YOUR EXERCISE OF ANY RIGHTS GRANTED TO YOU FOR THE CODE, CLOUDERA IS NOT LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE OR ONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED TO, DAMAGES RELATED TO LOST REVENUE, LOST PROFITS, LOSS OF INCOME, LOSS OF BUSINESS ADVANTAGE OR UNAVAILABILITY, OR LOSS OR CORRUPTION OF DATA.
#
# --------------------------------------------------------------------------------------
function scan_for_jndi {
local targetdir=${1:-/opt/cloudera}
echo "Running on '$targetdir'"
patterns=(JndiLookup.class)
good_pattern=ClassArbiter.class
if [ -n "$LOG4J_VERSION" ]; then
if [[ $LOG4J_VERSION == "1.x" ]]; then
patterns=(JMSAppender.class SocketAppender.class SMTPAppender.class JDBCAppender.class)
good_pattern="y1y2y3"
fi
fi
for jarfile in $(find -L $targetdir -name "*.jar" -o -name "*.tar"); do
if [ -L "$jarfile" ]; then
continue
fi
for pattern in "${patterns[@]}"; do
if grep -q $pattern $jarfile; then
if is_log4j_vulnerable $jarfile; then
echo "Vulnerable version $log4j_version ($pattern) of Log4j-core found in '$jarfile'"
exitcode=1
else
echo "Fixed version $log4j_version of Log4j-core found in '$jarfile'"
fi
fi
done
# Is this jar in jar (uber-jars)?
if unzip -l $jarfile | grep -v 'Archive:' | grep '\.jar$' >/dev/null; then
for inner in $(unzip -l $jarfile | grep -v 'Archive:' | grep '\.jar$' | awk '{print $4}'); do
for pattern in "${patterns[@]}"; do
if unzip -p $jarfile $inner | grep -q $pattern; then
# Unzip the log4j jar to a temporary directory once JndiLookup is detected
rm -r -f $tmpdir/unzip_target
mkdir $tmpdir/unzip_target
unzip -qq $jarfile $inner -d $tmpdir/unzip_target
for log4j_jarfile in $(grep -r -l $pattern $tmpdir/unzip_target); do
if is_log4j_vulnerable $log4j_jarfile; then
echo "Vulnerable version $log4j_version ($pattern) of Log4j-core found in inner '$inner' of '$jarfile'"
exitcode=1
else
echo "Fixed version $log4j_version of Log4j-core found in inner '$inner' of '$jarfile'"
fi
done
rm -r -f $tmpdir/unzip_target
fi
done
done
fi
done
for warfile in $(find -L $targetdir -name "*.war" -o -name "*.nar"); do
if [ -L "$warfile" ]; then
continue
fi
rm -r -f $tmpdir/unzip_target
mkdir $tmpdir/unzip_target
unzip -qq $warfile -d $tmpdir/unzip_target
for pattern in "${patterns[@]}"; do
for jarfile in $(grep -r -l $pattern $tmpdir/unzip_target); do
if is_log4j_vulnerable $jarfile; then
echo "Vulnerable version $log4j_version ($pattern) of Log4j-core found in '$warfile'"
exitcode=1
else
echo "Fixed version $log4j_version of Log4j-core found in '$warfile'"
fi
done
done
rm -r -f $tmpdir/unzip_target
done
for tarfile in $(find -L $targetdir -name "*.tar.gz" -o -name "*.tgz"); do
if [ -L "$tarfile" ]; then
continue
fi
for pattern in "${patterns[@]}"; do
if zgrep -q $pattern $tarfile; then
rm -r -f $tmpdir/untar_target
mkdir $tmpdir/untar_target
tar xf "$tarfile" -C "$tmpdir/untar_target"
for jarfile in $(grep -r -l $pattern $tmpdir/untar_target); do
if is_log4j_vulnerable $jarfile; then
echo "Vulnerable version $log4j_version ($pattern) of Log4j-core found in '$tarfile'"
exitcode=1
else
echo "Fixed version $log4j_version of Log4j-core found in '$tarfile'"
fi
done
rm -r -f $tmpdir/untar_target
fi
done
done
echo "Scan complete"
}
function is_log4j_vulnerable {
# As a side note, we return 0 when the log4j-core jar is vulnerable.
local log4j_jar=$1
local log4j_pom_prop=$(unzip -l $log4j_jar | grep "log4j-core.*pom.properties" | awk '{print $4}'| head -1)
log4j_version=UNKNOWN
log4j_pattern="^2\.([0-9]|(1[0-5]))\."
if [ -n "$LOG4J_VERSION" ]; then
if [[ $LOG4J_VERSION == "2.17.1" ]]; then
log4j_pattern="^2\.([0-9]|(1[0-6]))\."
elif [ $LOG4J_VERSION == "1.x" ]; then
log4j_pom_prop=$(unzip -l $log4j_jar | grep "log4j.*pom.properties" | awk '{print $4}'| head -1)
log4j_pattern="^1\.([0-9]|(1[0-8]))\."
fi
fi
if [ -z $log4j_pom_prop ]; then
echo "pom.properties file is not found in '$log4j_jar'. Failed to determine the version of log4j-core."
# As a fallback plan, try to find ClassArbiter.class
if grep -q $good_pattern $log4j_jar; then
echo "Fallback to good pattern based search and a valid pattern is found in '$log4j_jar'. Hence log4j version is 2.16 or above."
return 1
else
return 0
fi
else
log4j_version=$(unzip -p $log4j_jar $log4j_pom_prop | grep -i ^version | awk -F= '{print $2}')
if [[ $log4j_version =~ $log4j_pattern ]]; then
# Any version between 2.0.* to 2.15.* is vulnerable.
return 0
else
return 1
fi
fi
}
function delete_jndi_from_jar_files {
local _sha256sum_org
local _sha256sum_backup
local targetdir=${1:-/opt/cloudera}
echo "Running on '$targetdir'"
local backupdir=${2:-/opt/cloudera/log4shell-backup}
mkdir -p "$backupdir"
echo "Backing up files to '$backupdir'"
patterns=(JndiLookup.class)
if [ -n "$LOG4J_VERSION" ]; then
if [[ $LOG4J_VERSION == "1.x" ]]; then
patterns=(JMSAppender.class SocketAppender.class SMTPAppender.class JDBCAppender.class)
fi
fi
for jarfile in $(find -L $targetdir -name "*.jar"); do
multipleDeletes=0
if [ -L "$jarfile" ]; then
continue
fi
for pattern in "${patterns[@]}"; do
if grep -q $pattern $jarfile; then
# set multipleDeletes variable to 1 so that when one more pattern is detected, we can avoid
# checksum verifications.
multipleDeletes=1
# Backup file only if backup doesn't already exist
mkdir -p "$backupdir/$(dirname $jarfile)"
local targetbackup="$backupdir/$jarfile.backup"
if [ ! -f "$targetbackup" ]; then
echo "Backing up to '$targetbackup'"
cp -f "$jarfile" "$targetbackup"
else
echo "Backup file exists: ${targetbackup} - skipping backup"
fi
# Check the backup matches the original before altering it
if [ 1 -eq $multipleDeletes ]; then
# Rip out class without doing checksum as we already deleted some other vulnarable classes
echo "Deleting $pattern from '$jarfile'"
zip -q -d "$jarfile" */$pattern
else
_sha256sum_org=$(sha256sum ${jarfile} | awk -F' ' '{print $1}')
_sha256sum_backup=$(sha256sum ${targetbackup} | awk -F' ' '{print $1}')
if [ "${_sha256sum_org}" = "${_sha256sum_backup}" ] ; then
# Rip out class
echo "Deleting $pattern from '$jarfile'"
zip -q -d "$jarfile" */$pattern
else
echo "Backup of file ${jarfile} doesn't match ${targetbackup}"
echo "NOT removing $pattern from ${jarfile}"
exit 1
fi
fi
fi
done
# Is this jar in jar (uber-jars)?
if unzip -l $jarfile | grep -v 'Archive:' | grep '\.jar$' >/dev/null; then
for pattern in "${patterns[@]}"; do
for inner in $(unzip -l $jarfile | grep -v 'Archive:' | grep '\.jar$' | awk '{print $4}'); do
if unzip -p $jarfile $inner | grep -q $pattern; then
# Backup file only if backup doesn't already exist
mkdir -p "$backupdir/$(dirname $jarfile)"
local targetbackup="$backupdir/$jarfile.backup"
if [ ! -f "$targetbackup" ]; then
echo "Backing up to '$targetbackup'"
cp -f "$jarfile" "$targetbackup"
else
echo "Backup file exists: ${targetbackup} - skipping backup"
fi
TMP_DIR=$(mktemp -d)
pushd $TMP_DIR
unzip -q $jarfile $inner
echo "Deleting $pattern in nested jar $inner of $jarfile"
zip -q -d $inner \*/$pattern
zip -qur $jarfile .
popd
rm -rf $TMP_DIR
fi
done
done
fi
done
echo "Completed removing JNDI from jar files"
for narfile in $(find -L $targetdir -name "*.war" -o -name "*.nar"); do
if [ -L "$narfile" ]; then
continue
fi
doZip=0
rm -r -f $tmpdir/unzip_target
mkdir $tmpdir/unzip_target
unzip -qq $narfile -d $tmpdir/unzip_target
for jarfile in $(find -L $tmpdir/unzip_target -name "*.jar"); do
if [ -L "$jarfile" ]; then
continue
fi
for pattern in "${patterns[@]}"; do
if grep -q $pattern $jarfile; then
# Backup file only if backup doesn't already exist
mkdir -p "$backupdir/$(dirname $jarfile)"
targetbackup="$backupdir/$jarfile.backup"
if [ ! -f "$targetbackup" ]; then
echo "Backing up to '$targetbackup'"
cp -f "$jarfile" "$targetbackup"
fi
# Rip out class
echo "Deleting $pattern from '$jarfile'"
zip -q -d "$jarfile" \*/$pattern
doZip=1
fi
done
done
if [ 1 -eq $doZip ]; then
tempfile=$(mktemp -u)
echo "Updating '$narfile'"
pushd $tmpdir/unzip_target
zip -r -q $tempfile .
popd
chown --reference="$narfile" "$tempfile"
chmod --reference="$narfile" "$tempfile"
mv -f "$tempfile" "$narfile"
rm -f $tempfile
fi
rm -r -f $tmpdir/unzip_target
done
echo "Completed removing JNDI from nar files"
}
function delete_jndi_from_targz_file {
tarfile=$1
if [ ! -f "$tarfile" ]; then
echo "Tar file '$tarfile' not found"
exit 1
fi
local backupdir=${2:-/opt/cloudera/log4shell-backup}
mkdir -p "$backupdir/$(dirname $tarfile)"
targetbackup="$backupdir/$tarfile.backup"
if [ ! -f "$targetbackup" ]; then
echo "Backing up to '$targetbackup'"
cp -f "$tarfile" "$targetbackup"
else
echo "Backup file exists: ${targetbackup} - skipping backup"
fi
echo "Patching '$tarfile'"
tempfile=$(mktemp)
tempdir=$(mktemp -d)
tempbackupdir=$(mktemp -d)
tar xf "$tarfile" -C "$tempdir"
delete_jndi_from_jar_files "$tempdir" "$tempbackupdir"
echo "Recompressing"
(cd "$tempdir" && tar czf "$tempfile" --owner=1000 --group=100 .)
# Restore old permissions before replacing original
chown --reference="$tarfile" "$tempfile"
chmod --reference="$tarfile" "$tempfile"
mv -f "$tempfile" "$tarfile"
rm -f $tempfile
rm -rf $tempdir
rm -rf $tempbackupdir
echo "Completed removing JNDI from $tarfile"
}
function delete_jndi_from_hdfs {
issecure="true"
if [ ! "$#" -eq 3 ]; then
echo "Invalid arguments. Please choose tar ball HDFS path, keytab file and filetype as 'mr' or 'tez'."
exit 1
fi
hdfs_path=$1
local keytab=$2
local file_type=$3
user_option=""
if [[ -z "$keytab" || ! -s $keytab ]]; then
echo "Keytab file is not found or is empty: $keytab. Considering this as a non-secure cluster deployment."
issecure="false"
user_option="sudo -u hdfs"
fi
if [ $issecure == "true" ]; then
echo "Using $keytab to access HDFS"
principal=$(klist -kt $keytab | egrep -v 'HTTP|host/' | tail -1 | awk '{print $4}')
if [ -z "$principal" ]; then
echo "principal not found: $principal"
exit 0
fi
kinit -kt $keytab $principal
fi
# In case two or more namenode host cleanups are running at exactly the same time, stagger the
# probe of the HDFS marker.
sleep $((1 + $RANDOM % 20))
hdfs dfs -test -e $hdfs_path
ret_status=$?
if [ $ret_status -eq 1 ]; then
echo "Tar ball is not available in $hdfs_path. $file_type is not installed."
return
fi
hdfs_file_path=$(hdfs dfs -ls $hdfs_path | tail -1 | awk '{print $8}')
if [[ ! $hdfs_file_path == *.tar.gz ]]; then
echo "Desired tar ball path was not found in HDFS. Exiting."
exit 0
fi
hdfs_lock_path="/user/upgrade-lock_${file_type}"
hdfs dfs -test -e $hdfs_lock_path
ret_status=$?
if [ $ret_status -eq 1 ]; then
$user_option hdfs dfs -touch $hdfs_lock_path
else
echo "Tar ball for $file_type in HDFS is already upgraded."
return 0
fi
permissions=$(hdfs dfs -stat "%a" $hdfs_file_path)
if [ -z $permissions ]; then
echo "Unable to fetch permissions for $hdfs_file_path . Exiting"
exit 1
fi
user_group=$(hdfs dfs -stat "%u:%g" $hdfs_file_path)
if [ -z $user_group ]; then
echo "Unable to fetch user and group for $hdfs_file_path . Exiting"
exit 1
fi
current_time=$(date "+%Y.%m.%d-%H.%M.%S")
echo "Current Time : $current_time"
local_path="$tmpdir/hdfs_tar_files.${current_time}"
mkdir -p $local_path
echo "Downloading tar ball from HDFS path $hdfs_file_path to $local_path"
echo "Printing current HDFS file stats"
hdfs dfs -ls $hdfs_file_path
hdfs dfs -get -f $hdfs_file_path $local_path
hdfs_bc_path="$tmpdir/backup.${current_time}"
echo "Taking a backup of HDFS dir $hdfs_file_path to $hdfs_bc_path"
$user_option hdfs dfs -mkdir -p $hdfs_bc_path
$user_option hdfs dfs -cp -f $hdfs_file_path $hdfs_bc_path
out="$(basename $local_path/*)"
local_full_path="${local_path}/${out}"
echo "Executing the log4j removal script"
delete_jndi_from_targz_file $local_full_path
echo "Completed executing log4j removal script and uploading $out to $hdfs_file_path"
$user_option hdfs dfs -copyFromLocal -f $local_full_path $hdfs_file_path
$user_option hdfs dfs -chown $user_group $hdfs_file_path
$user_option hdfs dfs -chmod $permissions $hdfs_file_path
echo "Printing updated HDFS file stats"
hdfs dfs -ls $hdfs_file_path
if [ $issecure == "true" ]; then
which kdestroy && kdestroy
fi
}
function usage() {
cat << EOF
Search for and remove instances of the log4j security hole within Cloudera artifacts.
OPTIONAL PARAMETERS
target directory: The installed path of Cloudera software, default=/opt/cloudera
backupdir: Where the original jar and tar.gz files will be saved, default /opt/cloudera/log4shell-backup
EOF
}
targetdir=${1:-/opt/cloudera}
backupdir=${2:-/opt/cloudera/log4shell-backup}
platform=${3:-common}
parcelfile=${4:-""}
tmpdir=${TMPDIR:-/tmp}
mkdir -p $tmpdir
echo "Using tmp directory '$tmpdir'"
if ! command -v unzip &> /dev/null; then
echo "unzip not found. unzip is required to run this script."
exit 1
fi
if ! command -v zgrep &> /dev/null; then
echo "zgrep not found. zgrep is required to run this script."
exit 1
fi
if ! command -v zip &> /dev/null; then
echo "zip not found. zip is required to run this script."
exit 1
fi
if ! command -v sha256sum &> /dev/null; then
echo "sha256sum not found. sha256sum is required to run this script."
exit 1
fi
if [ -z "$SKIP_JAR" ]; then
echo "Removing JNDI from jar files"
delete_jndi_from_jar_files $targetdir $backupdir
else
echo "Skipped patching .jar"
fi
if [ -z "$SKIP_TGZ" ]; then
echo "Removing JNDI from tar.gz files"
log4jpattern=JndiLookup.class
if [ -n "$LOG4J_VERSION" ]; then
if [[ $LOG4J_VERSION == "1.x" ]]; then
log4jpattern=JMSAppender.class
fi
fi
for targzfile in $(find -L $targetdir -name '*.tar.gz') ; do
if zgrep -q $log4jpattern $targzfile; then
echo "$log4jpattern found in $targzfile, repacking"
delete_jndi_from_targz_file $targzfile $backupdir
fi
done
else
echo "Skipped patching .tar.gz"
fi
if [ -z "$SKIP_HDFS" ]; then
if [ $platform == "ibm" ]; then
if ps -efww | grep org.apache.hadoop.hdfs.server.namenode.NameNode | grep -v grep 1>/dev/null 2>&1; then
echo "Found an HDFS namenode on this host, removing JNDI from HDFS tar.gz files for platform='$platform'"
keytab_file="nn.service.keytab"
keytab=$(find /etc/security/keytabs/ -type f -iname $keytab_file |tail -1)
delete_jndi_from_hdfs "/user/tez/*" $keytab "tez"
delete_jndi_from_hdfs "/user/yarn/mapreduce/mr-framework/" $keytab "mr"
fi
elif [ $platform == "dell" ]; then
if ps -efww | grep org.apache.hadoop.yarn.server.resourcemanager.ResourceManager | grep -v grep 1>/dev/null 2>&1; then
echo "Found an Resourcemanager on this host, removing JNDI from HDFS tar.gz files for platform='$platform'"
keytab_file="hdfs.headless.keytab"
keytab=$(find /etc/security/keytabs/ -type f -iname $keytab_file |tail -1)
if [[ -z "$keytab" || ! -s $keytab ]]; then
echo "If this is a secure cluster, please ensure that /etc/security/keytabs/hdfs.headless.keytab is present for DELL."
fi
delete_jndi_from_hdfs "/user/tez/*" $keytab "tez"
delete_jndi_from_hdfs "/user/yarn/mapreduce/mr-framework/" $keytab "mr"
fi
else
if ps -efww | grep org.apache.hadoop.hdfs.server.namenode.NameNode | grep -v grep 1>/dev/null 2>&1; then
echo "Found an HDFS namenode on this host, removing JNDI from HDFS tar.gz files for platform='$platform'"
keytab_file="hdfs.keytab"
keytab=$(find /var/run/cloudera-scm-agent/process/ -type f -iname $keytab_file | grep -e NAMENODE -e DATANODE | tail -1)
delete_jndi_from_hdfs "/user/tez/*" $keytab "tez"
delete_jndi_from_hdfs "/user/yarn/mapreduce/mr-framework/" $keytab "mr"
fi
fi
else
echo "Skipped patching .tar.gz in HDFS"
fi
exitcode=0
if [ -n "$RUN_SCAN" ]; then
echo "Running scan for missed JndiLookup classes. This may take a while."
if [ -n "$parcelfile" ]; then
# Override target-dir with parcel-file path.
echo "Override targetdir='$targetdir' as -f option set parcelfile to '$parcelfile'"
parceldir=$(mktemp -d)
now=$(date +"%T")
echo "Start to unpack the parcelfile at $now"
tar zxf $parcelfile -C "$parceldir"
now=$(date +"%T")
echo "Completed unpacking the parcelfile and start scanning at $now"
scan_for_jndi $parceldir
now=$(date +"%T")
echo "Completed scan at $now"
rm -rf $parceldir
else
scan_for_jndi $targetdir
fi
if [ -n "$EXIT_ON_FAIL" ]; then
exit $exitcode
fi
fi