-
Notifications
You must be signed in to change notification settings - Fork 36
/
pre-receive.sh
executable file
·458 lines (414 loc) · 17.6 KB
/
pre-receive.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
#!/usr/bin/env bash
#
#########################################################
## BSD 3-Clause License
##
## Copyright (c) 2019, Dennis Kennedy and Simeon Cloutier
##
## Redistribution and use in source and binary forms, with or without modification,
## are permitted provided that the following conditions are met:
##
## 1. Redistributions of source code must retain the above copyright notice, this
## list of conditions and the following disclaimer.
##
## 2. Redistributions in binary form must reproduce the above copyright notice,
## this list of conditions and the following disclaimer in the documentation and/or
## other materials provided with the distribution.
##
## 3. Neither the name of the copyright holder nor the names of its contributors
## may be used to endorse or promote products derived from this software without
## specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
## ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
## WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
## IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
## INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
## LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
## OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
## OF THE POSSIBILITY OF SUCH DAMAGE.
#########################################################
####
#### Script name: pre-receive.sh
#### Authored by: Dennis Kennedy and Simeon Cloutier
#### Description: The SEDATED pre-receive Git hook script used in conjunction with
#### SEDATED's regexes (config/regexes.json), identifies added or modified
#### lines of code being pushed to a Git instance that contain hard-coded
#### credentials/sensitive data (as identified in config/regexes.json) and
#### prevents the push IF lines containing hard-coded credentials/sensitive
#### data are found.
####
start_time=$(date +%s%N)
execution_time_ms=0
my_path=${0%/*}
regexes=${my_path}/config/regexes.json
commit_whitelist=${my_path}/config/whitelists/commit_whitelist.txt
repo_whitelist=${my_path}/config/whitelists/repo_whitelist.txt
enforced_repos_list=${my_path}/config/enforced_repos_list.txt
custom_configs=${my_path}/config/custom_configs.sh
commit_whitelisted_push=false
repo_whitelisted_push=false
zero_commit="0000000000000000000000000000000000000000"
total_violations=0
file_violations=0
num_of_branches=0
branch_violations=0
branch=""
echo " ___ ___ ___ _ _____ ___ ___ "
echo " / __| __| \ / \_ _| __| \ (R)"
echo " \__ \ _|| |) / A \| | | _|| |) |"
echo " |___/___|___/_/ \_\_| |___|___/ "
# Check if custom_configs.sh exists in config/
if [[ -f "$custom_configs" ]]; then
source "$custom_configs"
else
# Action is required to create custom_configs.sh from custom_configs.sh.example
echo ">>>>>>>>>> ACTION REQUIRED <<<<<<<<<<"
echo ">>>>> Cannot find config/custom_configs.sh please use the template at..."
echo ">>>>> https://github.com/owasp/sedated and populate the desired variables/functions."
echo ">>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<"
exit 1
fi
if [[ "$show_SEDATED_link_custom" == "True" ]]; then
echo " https://github.com/owasp/sedated"
fi
echo "" # Blank line for formatting
function PRINT_SCRIPT_EXECUTION_TIME() {
local execution_time_ms=$((($(date +%s%N) - $start_time)/1000000))
echo "$execution_time_ms ms"
}
function PRINT_HAPPY_CREDENTIAL_PANDA() {
echo " .--._____.--. "
echo " ( /_.) (._\ ) "
echo " __ | / o \ | __ "
echo " (oo\ \ \_-_/ / /oo) "
echo " \ \__/ \__/ / "
echo " \ / \ / "
}
function PRINT_PUSH_ACCEPTED_MESSAGE() {
echo "Push ACCEPTED"
echo "========================================"
}
function PRINT_ERROR_PANDA() {
echo " .--._____.--. "
echo " ( /_+) (x_\ ) "
echo " __ | / o \ | __ "
echo " (oo\ \ \_~_/ / /oo) "
echo " \ \__/ \__/ / "
echo " \ / ERROR \ / "
echo "========================================"
}
# Check for regex grep error in previous command
function REGEX_GREP_ERROR_CHECK() {
local return_val="$?"
if [[ "$return_val" != "0" && "$return_val" != "1" ]]; then
PRINT_ERROR_PANDA
echo "Please try again. SEDATED was unable to complete its scan."
echo "----------------------------------------"
echo "$documentation_link_custom"
echo "========================================"
exit 1
fi
}
# Checks if the current repo SEDATED is running on is supposed to have
# the SEDATED scan performed and enforced or just exit 0 and print message.
function ENFORCED_REPO_CHECK() {
local enforced=$(cat "$enforced_repos_list" | grep -E "^[[:blank:]]*($user_group_name\/\*)[[:blank:]]*$|^[[:blank:]]*($user_group_name\/$repo_name)[[:blank:]]*$")
if [[ ! "$enforced" ]]; then
echo "================================================"
echo "$enforced_repo_check_true_message_custom"
echo "$documentation_link_custom"
echo "================================================"
PRINT_SCRIPT_EXECUTION_TIME
exit 0
fi
}
# Append branch name and commit ids to commits_and_branches variable
# $1 is the list of commit ids
# $2 is current commits_and_branches value
function APPEND_TO_COMMITS_AND_BRANCHES() {
# Check if list_of_commits is empty
if [[ "$1" ]]; then
# Check if commits_and_branches exists so newline is not added as the first line
if [[ "$2" ]]; then
# newline prevents two commit ids from being added to the same line
commits_and_branches+=$'\n'
fi
commits_and_branches+="$branch_name"
commits_and_branches+=$'\n'
commits_and_branches+="$1"
((num_of_branches++))
fi
}
# Collects all of the commits and branches included in the push and stores in commits_and_branches variable
function GET_PUSHED_COMMIT_IDS_AND_BRANCHES() {
local pull_request_from_org="${GITHUB_PULL_REQUEST_HEAD%:*}"
local pull_request_to_org="${GITHUB_PULL_REQUEST_BASE%:*}"
# Check if the latest commit id is all 0's, if it is there is nothing to scan
if [[ "$latest_commit_id" == "$zero_commit" ]]; then
: # do nothing
# Check if it is a pull request from a fork
elif [[ "$pull_request_from_org" != "$pull_request_to_org" ]]; then
# From git history list all commits between the base commit id and the newest commit id
list_of_commits=$(git rev-list $base_commit_id..$latest_commit_id)
APPEND_TO_COMMITS_AND_BRANCHES "$list_of_commits" "$commits_and_branches" "$branch_name"
else
# Check if base commit id is all 0's, meaning this is the initial commit/push
if [[ "$base_commit_id" == "$zero_commit" ]]; then
# From git history list all commit ids up to and including the newest commit id
# --not --all prevents SEDATED from looping ALL commit ids when new branches
# are created and pushed, allows it to only loop new commit ids
list_of_commits=$(git rev-list $latest_commit_id --not --all)
APPEND_TO_COMMITS_AND_BRANCHES "$list_of_commits" "$commits_and_branches" "$branch_name"
else
# List all commit ids between the base commit id and the newest commit id
list_of_commits=$(git rev-list $base_commit_id..$latest_commit_id --not --all)
APPEND_TO_COMMITS_AND_BRANCHES "$list_of_commits" "$commits_and_branches" "$branch_name"
fi
fi
}
# This reads in a variable and creates a ' ' (space) delimited array from it
# $1 is the variable to read in from
function CREATE_ARRAY() {
commits_and_branches_array+=($1)
}
# Prints execution time to console, executes EXIT_SEDATED_CUSTOM, then exits SEDATED
# $1 is the exit status, should be equal to 0 or 1
function EXIT_SEDATED() {
PRINT_SCRIPT_EXECUTION_TIME
EXIT_SEDATED_CUSTOM
exit $1
}
# Check if a line contains a filename from patch file
# $1 is the line to be checked
function CHECK_IF_LINE_CONTAINS_FILENAME() {
temp_filename=$(echo "$1" | grep -Po '^\+\+\+ b/\K(.*)')
if [[ "$temp_filename" ]]; then
filename="$temp_filename"
file_violations=0
fi
}
# Check if commit id is a commit id or branch name
# $1 is the commit id to be checked
function CHECK_IF_COMMIT_ID_IS_BRANCH_NAME() {
if [[ ! "$1" =~ ^[a-z0-9]{40}$ ]]; then
branch="$1"
branch_violations=0
return 0
fi
return 1
}
function PRINT_ON_BRANCH_MESSAGE() {
echo "====================================================="
echo "On branch -> $branch"
}
# Check if current commit id being scanned is whitelisted
# $1 commit id currently being scanned... "${commit_id}"
function CHECK_IF_COMMIT_ID_IS_WHITELISTED() {
if [[ -f "$commit_whitelist" ]]; then # check if config/whitelists/commit_whitelist.txt file exists
commit_whitelisted=$(cat "$commit_whitelist" | grep -E "^[[:blank:]]*($1)[[:blank:]]*$")
fi
if [[ "$commit_whitelisted" ]]; then
if [[ "$branch_violations" == 0 && "$branch" ]]; then
PRINT_ON_BRANCH_MESSAGE
violation_commit_id_array=()
((branch_violations++))
fi
echo "------------ ** WHITELISTED COMMIT ** ---------------"
echo ">>>>>> $1 <<<<<"
commit_whitelisted_push=true
return 0
fi
return 1
}
# Check if current repo being scanned is whitelisted
function CHECK_IF_REPO_IS_WHITELISTED() {
if [[ -f "$repo_whitelist" ]]; then # check if config/whitelists/repo_whitelist.txt file exists
repo_whitelisted=$(cat "$repo_whitelist" | grep -E "^[[:blank:]]*($user_group_name\/$repo_name)[[:blank:]]*$")
else
PRINT_ERROR_MESSAGE_CUSTOM "UNABLE TO FIND REPO WHITELIST FILE"
UNABLE_TO_ACCESS_REPO_WHITELIST_CUSTOM
fi
if [[ "$repo_whitelisted" ]]; then
repo_whitelisted_push=true
echo "========================================"
PRINT_HAPPY_CREDENTIAL_PANDA
echo "$user_group_name/$repo_name has been whitelisted"
PRINT_PUSH_ACCEPTED_MESSAGE
PUSH_ACCEPTED_CUSTOM
EXIT_SEDATED 0
fi
}
# Create pipe delimited regex_string from regexes json output
function CREATE_PIPE_DELIMITED_REGEX_STRING() {
if [[ -f "$regexes" ]]; then # check if config/regexes.json file exists
regex_string=$(cat "${regexes}" | grep -Po ':[[:space:]]*\"[[:space:]]*\K(.*)' | sed 's/[[:space:]]*"[[:space:]]*}[[:space:]]*,/|/' | tr -d '\n' | sed 's/\\\\/\\/g' | sed '$s/"}$//' )
fi
# If no regexes included in the config/regexes.json file
if [[ ! "$regex_string" ]]; then
PRINT_ERROR_MESSAGE_CUSTOM "UNABLE TO ACCESS REGEXES"
UNABLE_TO_ACCESS_REGEXES_CUSTOM
EXIT_SEDATED 1
fi
}
# $1 is commit ids included in push
# $2 is list of commit ids included in push
function PRINT_INITIAL_SEDATED_MESSAGE() {
echo "Repo Name = $user_group_name/$repo_name"
echo "========================================"
echo "***Commits scanned ($1 total):"
echo "$2"
echo "========================================"
echo "Violations listed, by line, below (results may be obfuscated):"
}
# $1 is total number of lines with sensitive data
# $2 is documentation link
function PRINT_PUSH_REJECTED_WITH_VIOLATIONS_MESSAGE() {
PUSH_REJECTED_WITH_VIOLATIONS_CUSTOM
echo "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
echo "This push contains sensitive data and needs your attention."
echo " .--._____.--. "
echo " ( /_x) (x_\ ) "
echo " __ | / o \ | __ "
echo " (oo\ \ \_-_/ / /oo) "
echo " \ \__/ \__/ / "
echo " \ / \ / "
echo "$1 lines found with sensitive data"
echo "Push REJECTED."
echo "=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="
echo "Please remove any sensitive data from the flagged commit ID(s)"
echo "For more information go to:"
echo "$2"
echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
echo "============================================================="
EXIT_SEDATED 1
}
# Subtract branches from total number of commits when multiple branches
# are included in a single push so branch count is not included in commit count
# $1 is length of array
# $2 is num_of_branches
function CALCULATE_NUM_COMMITS() {
if [[ "$num_of_branches" > 1 ]]; then
total_num_of_commits=$(($1-$2))
else
total_num_of_commits="$1"
fi
}
function PRINT_NO_COMMITS_MESSAGE() {
echo "========================="
PRINT_HAPPY_CREDENTIAL_PANDA
echo "No commits to traverse"
echo "Push ACCEPTED"
echo "========================="
}
# If only commit id included is all 0's then nothing to scan
# or if only branches and no commit ids then exit SEDATED
# $1 is the list of commit ids/branches in push
function CHECK_FOR_COMMIT_IDS_TO_SCAN() {
if [[ ! "$1" || ! $(echo "$1" | grep -E '^[a-z0-9]{40}$') ]]; then
PRINT_NO_COMMITS_MESSAGE
EXIT_SEDATED 0
fi
}
# Remove branch name from commits_and_branches list if only a single branch is being pushed
# $1 is number of branches included in push
# $2 list of branches and commit ids
function REMOVE_BRANCH_NAME_IF_SINGE_BRANCH_PUSH() {
if [[ "$1" == 1 ]]; then
commits_and_branches=$(echo "$2" | grep -Ev "^$|^.*/.*/.*$")
fi
}
function MAIN() {
# Get org/user/group name as well as repo name minus the .git file ext.
SET_USER_REPO_NAME_CUSTOM
# When enabled globally allows selective enforcement as controlled
# by the config/enforced_repos_list.txt config file
if [[ "$use_enforced_repo_check_custom" == "True" ]]; then
ENFORCED_REPO_CHECK
elif [[ "$use_enforced_repo_check_custom" == "False" ]]; then
: # No additional action is REQUIRED
else
# Action is required to create custom_configs.sh from custom_configs.sh.example
echo ">>>>>>>>>> ACTION REQUIRED <<<<<<<<<<"
echo ">>>>> Please set the use_enforced_repo_check_custom variable in..."
echo ">>>>> config/custom_configs.sh to \"True\" or \"False\""
echo ">>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<"
exit 1
fi
# If repo is in config/whitelists/repo_whitelist.txt file, accept push and exit SEDATED℠
CHECK_IF_REPO_IS_WHITELISTED
# Loop through each branch being pushed
while read -r base_commit_id latest_commit_id branch_name; do
GET_PUSHED_COMMIT_IDS_AND_BRANCHES
done
CHECK_FOR_COMMIT_IDS_TO_SCAN "$commits_and_branches"
REMOVE_BRANCH_NAME_IF_SINGE_BRANCH_PUSH "$num_of_branches" "$commits_and_branches"
CREATE_ARRAY "$commits_and_branches"
CALCULATE_NUM_COMMITS "${#commits_and_branches_array[*]}" "$num_of_branches"
PRINT_INITIAL_SEDATED_MESSAGE "$total_num_of_commits" "$commits_and_branches"
# Create pipe delimited regex_string from regexes
CREATE_PIPE_DELIMITED_REGEX_STRING
# Check if commit whitelist can be accessed, but don't exit
if [[ ! -f "$commit_whitelist" ]]; then
PRINT_ERROR_MESSAGE_CUSTOM "UNABLE TO FIND COMMIT WHITELIST FILE"
UNABLE_TO_ACCESS_COMMIT_WHITELIST_CUSTOM
fi
# Loop through each commit id in the push
for commit_id in "${commits_and_branches_array[@]}"; do
CHECK_IF_COMMIT_ID_IS_BRANCH_NAME "${commit_id}"
# Exit code 0 returned by function means commit id is branch name not a commit id
local branch_name_check_exit_code=$?
# If not a commit id and instead is a branch name (i.e. exit code 0) continue to next commit id
if [[ "$branch_name_check_exit_code" == 0 ]]; then continue; fi
CHECK_IF_COMMIT_ID_IS_WHITELISTED "${commit_id}"
# Exit code 0 returned by function means commit id is whitelisted
local whitelisted_exit_code=$?
# If commit id is whitelisted (i.e. exit code 0) break out of loop to next commit id
if [[ "$whitelisted_exit_code" == 0 ]]; then continue; fi
# Loops the git patch files and checks for everything that begins with "+"
# which denotes any new/modified lines of code. It then takes those results
# and bounces against the regexes.
local all_added_contents=$(git show -D "${commit_id}" | grep -E '^[\+]' | grep -P "${regex_string}" 2> /dev/null)
REGEX_GREP_ERROR_CHECK # Checks if line length exceeds grep PCRE's backtracking limit or other grep error, if true throw error and exit 1
if [[ "$all_added_contents" ]]; then
while read line; do
CHECK_IF_LINE_CONTAINS_FILENAME "${line}"
# If line is not a filename
if [[ ! "$temp_filename" ]]; then
# If first violation in this file print filename
if [[ "$file_violations" == 0 ]]; then
# If first violation in commit OR commit id not already flagged for a violation
if [[ ! "${violation_commit_id_array[@]}" || "${violation_commit_id_array[-1]}" != "${commit_id}" ]]; then
if [[ "$branch_violations" == 0 && "$branch" ]]; then
PRINT_ON_BRANCH_MESSAGE
fi
violation_commit_id_array+=("${commit_id}")
echo "-----------------------------------------------------"
echo "In commit -> ${commit_id}"
fi
echo "------------ $filename ------------"
fi
OBFUSCATE_CUSTOM "${line:0:150}"
echo "${masked}"
((branch_violations++))
((total_violations++))
((file_violations++))
fi
done <<< "$all_added_contents"
fi
done
# If violations flagged reject push; else accept push; print respective messages
if [[ "${total_violations}" -gt 0 ]]; then
PRINT_PUSH_REJECTED_WITH_VIOLATIONS_MESSAGE "$total_violations" "$documentation_link_custom"
else
echo "NONE"
PRINT_HAPPY_CREDENTIAL_PANDA
echo "Good job! No sensitive data found in your commit(s)!"
PRINT_PUSH_ACCEPTED_MESSAGE
PUSH_ACCEPTED_CUSTOM
EXIT_SEDATED 0
fi
}
MAIN