-
Notifications
You must be signed in to change notification settings - Fork 48
176 lines (150 loc) · 7.54 KB
/
protectSecurityRelevantCode.yml
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
name: Protect security-critical code/system
# - ensures that the systems in place to guarantee audits, approvals, versioning, test coverage etc. cannot be easily deactivated
# or altered without approval of the Information Security Manager (or CTO)
# - protects any git actions in the folder .github/workflows/*
# - protects the pre-commit checker script stored in .husky/pre-commit
on:
pull_request_review:
types: [submitted]
jobs:
protect-critical-code:
if: ${{ github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/[email protected]
with:
fetch-depth: 0 ##### Fetch all history for all branches
- name: Check Git Diff for protected files
id: check_protected_files
run: |
##### get all files modified by this PR
FILES="$(git diff --name-only origin/main HEAD)"
##### make sure that there are modified files
if [[ -z $FILES ]]; then
echo -e "\033[31mNo files found. This should not happen. Please check the code of the Github action. Aborting now.\033[0m"
echo "CONTINUE=false" >> $GITHUB_ENV
exit 1
fi
##### Initialize empty variables
PROTECTED_FILES=""
##### go through all modified file names/paths and identify contracts with path '.github/'
while IFS= read -r FILE; do
# Validate file exists
if [[ ! -f "$FILE" ]]; then
echo "Warning: File $FILE not found"
continue
fi
##### check for github actions and pre-commit checker paths
if echo "$FILE" | grep -iE '^\.github/|^\.husky/pre-commit'; then ##### modified git action found
PROTECTED_FILES="${PROTECTED_FILES}${FILE}"$'\n'
fi
done <<< "$FILES"
##### if none found, exit here as there is nothing to do
if [[ -z "$PROTECTED_FILES" ]]; then
echo -e "\033[32mThis PR does not change any security-relevant code.\033[0m"
echo -e "\033[32mNo further checks are required.\033[0m"
# set action output to false
echo "CONTINUE=false" >> $GITHUB_ENV
exit 0
else
##### set action output to true
echo -e "\033[31mThe following security-relevant files were are changed by this PR:\033[0m"
echo "$PROTECTED_FILES"
echo "CONTINUE=true" >> $GITHUB_ENV
fi
- name: Get "Information Security Manager" Group Members
if: env.CONTINUE == 'true'
env:
GH_PAT: ${{ secrets.GIT_ACTIONS_BOT_PAT_CLASSIC }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
##### unset the default git token (does not have sufficient rights to get team members)
unset GITHUB_TOKEN
##### use the Personal Access Token to log into git CLI
gh auth login --with-token < <(echo "$GH_PAT") || { echo "Failed to login with GitHub CLI"; exit 1; }
##### Function to get team members using github CLI
getTeamMembers() {
local org=$1
local team=$2
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/orgs/$org/teams/$team/members" | jq -r '.[].login'
}
ORG_NAME='lifinance'
GROUP_NAME='InformationSecurityManager'
##### get team members
INFORMATION_SECURITY_MEMBERS=$(getTeamMembers $ORG_NAME $GROUP_NAME)
echo "Team members of 'Information Security Manager' group: $INFORMATION_SECURITY_MEMBERS"
##### store members in variable
echo -e "$INFORMATION_SECURITY_MEMBERS" > itSec_git_handles.txt
- name: Check approval of Information Security Manager
id: check-sec-mgr-approval
uses: actions/github-script@v7
if: env.CONTINUE == 'true'
env:
PR_NUMBER: ${{ github.event.pull_request.number || github.event.review.pull_request.number }}
with:
script: |
const fs = require('fs');
// ANSI escape codes for colors (used for colored output in Git action console)
const colors = {
reset: "\033[0m",
red: "\033[31m",
green: "\033[32m",
yellow: "\033[33m",
};
// Read git handles from file
const itSecHandlesFile = 'itSec_git_handles.txt';
const itSecHandles = fs.readFileSync(itSecHandlesFile, 'utf-8').split(/\r?\n/).filter(Boolean);
if (!itSecHandles) {
console.log(`${colors.red}Could not get the git handles of the InformationSecurityManager team.${colors.reset}`);
core.setFailed("Cannot read from InformationSecurityManager team.");
return;
}
const pullNumber = process.env.PR_NUMBER;
if (!pullNumber) {
console.log(`${colors.red}No PR number found in context.${colors.reset}`);
core.setFailed("PR number is missing.");
return;
}
// get all reviewers that have approved this PR
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber,
});
// make sure that reviews are available
if(!reviews || reviews.length === 0) {
console.log(`${colors.red}Could not get reviewers of this PR from Github. Are there any reviews yet?${colors.reset}`);
console.log(`${colors.red}Check failed.${colors.reset}`);
core.setFailed("Required approval is missing");
return
}
// Filter to only include reviews that have "APPROVED" status
const approvedReviews = reviews.filter(review => review.state === 'APPROVED');
if(!approvedReviews.length) {
console.log(`${colors.red}Could not find any reviews with approval.${colors.reset}`);
console.log(`${colors.red}Cannot continue. Check failed.${colors.reset}`);
core.setFailed("Required approval is missing");
return
}
// extract the git login handles of all reviewers that approved this PR
const reviewerHandles = approvedReviews.map(review => review.user.login);
if(approvedReviews.length === 0)
console.log(`${colors.red}This PR has no approvals${colors.reset}`);
else
console.log(`This PR has been approved by the following git members: ${reviewerHandles}`);
// check if at least one of these reviewers is member of InformationSecurityManager group
if (reviewerHandles.some((handle) => itSecHandles.includes(handle))) {
console.log(`${colors.green}The current PR is approved by a member of the InformationSecurityManager group.${colors.reset}`);
console.log(`${colors.green}Check passed.${colors.reset}`);
core.setOutput('approved', 'true');
} else {
console.log(`${colors.red}The PR requires a missing approval by a member of the InformationSecurityManager group (https://github.com/orgs/lifinance/teams/InformationSecurityManager).${colors.reset}`);
console.log(`${colors.red}Check failed.${colors.reset}`);
core.setFailed("Required approval is missing");
}