-
Notifications
You must be signed in to change notification settings - Fork 1.3k
349 lines (328 loc) · 13.9 KB
/
Monitor Branch Protection Changes.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
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
name: Monitor Branch Protection Changes
on:
branch_protection_rule:
types: [created, edited, deleted]
schedule:
- cron: '0 0 * * *' # Runs at 00:00 UTC every day
workflow_dispatch:
issues:
types: [edited] # Trigger on issue edits
jobs:
check-tampering:
if: contains(github.event.issue.labels.*.name, 'branch-protection-state')
runs-on: ubuntu-latest
steps:
- name: Send Tampering Alert
uses: slackapi/[email protected]
env:
SLACK_BOT_TOKEN: ${{ secrets.BRANCH_PROTECTION_SLACK_BOT_TOKEN }}
with:
channel-id: 'C081N38PHC5'
payload: |
{
"text": "⚠️ SECURITY ALERT: Branch Protection State Issue Modified",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚨 *SECURITY ALERT: Branch Protection State Issue was manually modified!*\n\n*Repository:* ${{ github.repository }}\n*Modified by:* ${{ github.actor }}\n*Issue:* #${{ github.event.issue.number }}\n*Time:* ${{ github.event.issue.updated_at }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "⚠️ Manual modification of state issues may indicate an attempt to bypass branch protection monitoring. Please investigate immediately."
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Modified Issue",
"emoji": true
},
"url": "${{ github.event.issue.html_url }}",
"style": "danger"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Branch Settings",
"emoji": true
},
"url": "${{ github.server_url }}/${{ github.repository }}/settings/branches"
}
]
}
]
}
check-branch-protection:
# Don't run the normal check if this is an issue edit event
if: github.event_name != 'issues'
runs-on: ubuntu-latest
steps:
- name: Check Branch Protection Rules
id: check-rules
uses: actions/github-script@v7
with:
github-token: ${{ secrets.BRANCH_PROTECTION_PAT }}
script: |
const repo = context.repo;
try {
// Get repository info
console.log('Getting repository info...');
const repoInfo = await github.rest.repos.get({
owner: repo.owner,
repo: repo.repo
});
const defaultBranch = repoInfo.data.default_branch;
console.log(`Default branch is: ${defaultBranch}`);
// Get current protection rules
console.log(`Checking protection rules for ${defaultBranch}...`);
let currentRules;
try {
const protection = await github.rest.repos.getBranchProtection({
owner: repo.owner,
repo: repo.repo,
branch: defaultBranch
});
currentRules = protection.data;
console.log('Current protection rules:', JSON.stringify(currentRules, null, 2));
} catch (error) {
if (error.status === 404) {
console.log('No branch protection rules found');
currentRules = null;
} else {
console.log('Error getting branch protection:', error);
throw error;
}
}
// Find previous state issues
console.log('Finding previous state issues...');
const previousIssues = await github.rest.issues.listForRepo({
owner: repo.owner,
repo: repo.repo,
state: 'open',
labels: 'branch-protection-state',
per_page: 100
});
console.log(`Found ${previousIssues.data.length} previous state issues`);
let previousRules = null;
let changesDetected = false;
let changeDescription = '';
// Always create a new state issue if none exists
if (previousIssues.data.length === 0) {
console.log('No previous state issues found, creating initial state');
changesDetected = true;
changeDescription = 'Initial branch protection state';
} else {
// Get the most recent state
const mostRecentIssue = previousIssues.data[0];
try {
previousRules = JSON.parse(mostRecentIssue.body);
console.log('Successfully parsed previous rules');
// Compare states
const currentJSON = JSON.stringify(currentRules);
const previousJSON = JSON.stringify(previousRules);
if (currentJSON !== previousJSON) {
changesDetected = true;
console.log('Changes detected!');
if (!previousRules && currentRules) {
changeDescription = 'Branch protection rules were added';
} else if (previousRules && !currentRules) {
changeDescription = 'Branch protection rules were removed';
} else {
changeDescription = 'Branch protection rules were modified';
}
}
// Close all previous state issues
console.log('Closing previous state issues...');
for (const issue of previousIssues.data) {
await github.rest.issues.update({
owner: repo.owner,
repo: repo.repo,
issue_number: issue.number,
state: 'closed'
});
console.log(`Closed issue #${issue.number}`);
}
} catch (e) {
console.log('Error handling previous state:', e);
// If we can't parse previous state, treat as initial
changesDetected = true;
changeDescription = 'Initial branch protection state (previous state invalid)';
}
}
// Always create a new state issue
console.log('Creating new state issue...');
const newIssue = await github.rest.issues.create({
owner: repo.owner,
repo: repo.repo,
title: `Branch Protection State - ${new Date().toISOString()}`,
body: JSON.stringify(currentRules, null, 2),
labels: ['branch-protection-state']
});
console.log(`Created new state issue #${newIssue.data.number}`);
// Lock the issue immediately
await github.rest.issues.lock({
owner: repo.owner,
repo: repo.repo,
issue_number: newIssue.data.number,
lock_reason: 'resolved'
});
console.log(`Locked issue #${newIssue.data.number}`);
// Set outputs for notifications
core.setOutput('changes_detected', changesDetected.toString());
core.setOutput('change_description', changeDescription);
} catch (error) {
console.log('Error details:', error);
core.setFailed(`Error: ${error.message}`);
}
- name: Send Slack Notification - Branch Protection Event
if: github.event_name == 'branch_protection_rule'
uses: slackapi/[email protected]
env:
SLACK_BOT_TOKEN: ${{ secrets.BRANCH_PROTECTION_SLACK_BOT_TOKEN }}
with:
channel-id: 'C081N38PHC5'
payload: |
{
"text": "⚠️ Branch Protection Change Event Detected in ${{ github.repository }}",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "⚠️ Branch Protection Change Event Detected",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Repository:* ${{ github.repository }}\n*Triggered by:* ${{ github.actor }}\n*Event Type:* ${{ github.event.action }}\n*Workflow Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "⚠️ *Monitoring Notice:* A branch protection change event was triggered. If no change notification follows, this could indicate a non-working script or potential malicious activity."
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Branch Settings",
"emoji": true
},
"url": "${{ github.server_url }}/${{ github.repository }}/settings/branches"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Workflow Run",
"emoji": true
},
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}
]
}
- name: Send Slack Notification - Changes Detected
if: steps.check-rules.outputs.changes_detected == 'true'
uses: slackapi/[email protected]
env:
SLACK_BOT_TOKEN: ${{ secrets.BRANCH_PROTECTION_SLACK_BOT_TOKEN }}
with:
channel-id: 'C081N38PHC5'
payload: |
{
"text": "🚨 Branch protection rules changed in ${{ github.repository }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚨 *Branch protection rules changed!*\n\n*Repository:* ${{ github.repository }}\n*Changed by:* ${{ github.actor }}\n*Event:* ${{ github.event_name }}\n*Change:* ${{ steps.check-rules.outputs.change_description }}\n*Workflow Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Branch protection rules have changed. Check repository settings for details."
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Branch Settings",
"emoji": true
},
"url": "${{ github.server_url }}/${{ github.repository }}/settings/branches"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Workflow Run",
"emoji": true
},
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}
]
}
- name: Send Slack Notification - Error
if: failure()
uses: slackapi/[email protected]
env:
SLACK_BOT_TOKEN: ${{ secrets.BRANCH_PROTECTION_SLACK_BOT_TOKEN }}
with:
channel-id: 'C081N38PHC5'
payload: |
{
"text": "⚠️ Error monitoring branch protection in ${{ github.repository }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "⚠️ *Error monitoring branch protection!*\n\n*Repository:* ${{ github.repository }}\n*Workflow Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Error Details"
},
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
"style": "danger"
}
]
}
]
}