Skip to content

Commit 4c5e7d5

Browse files
authored
Merge pull request #90 from newrelic-trivy/ban-workflow
Ban workflow
2 parents de84405 + 2651cb1 commit 4c5e7d5

2 files changed

Lines changed: 140 additions & 1 deletion

File tree

.github/workflows/ban-user-org.yml

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
name: Ban user on /ban comment (org required workflow)
2+
3+
# Org-wide version of ban-user.yml, intended to run as a GitHub
4+
# "required workflow" so every repo in the org gets /ban without each
5+
# repo having to copy the file.
6+
#
7+
# Setup (org owner):
8+
# 1. Put this file in a repo in the org (e.g. <org>/.github-private or any
9+
# repo allowed to host required workflows).
10+
# 2. Settings → Actions → General → Required workflows → add this file and
11+
# target the repos it should apply to (typically: all repos).
12+
# 3. Register the GitHub App once at the org level and store the secrets as
13+
# organization secrets (Settings → Secrets and variables → Actions →
14+
# Organization secrets), scoped to the repos the required workflow runs in:
15+
# BAN_APP_ID — app id of a GitHub App installed on the org
16+
# BAN_APP_PRIVATE_KEY — PEM private key for that App
17+
#
18+
# App permissions:
19+
# Organization: Administration (read & write) — to block users
20+
# Repository: Metadata (read), Pull requests (read), Issues (write)
21+
#
22+
# The App must be installed on the org and granted access to every repo the
23+
# required workflow targets. The /ban command itself is gated on the
24+
# commenter having admin permission on the specific repo where the comment
25+
# was posted, so org-wide deployment does not widen who can ban.
26+
27+
on:
28+
issue_comment:
29+
types: [created]
30+
31+
permissions:
32+
issues: write
33+
pull-requests: write
34+
35+
jobs:
36+
ban:
37+
if: >
38+
github.event.issue.pull_request != null &&
39+
startsWith(github.event.comment.body, '/ban')
40+
runs-on: ubuntu-latest
41+
steps:
42+
- name: Validate /ban command
43+
id: validate
44+
env:
45+
BODY: ${{ github.event.comment.body }}
46+
run: |
47+
set -euo pipefail
48+
first_line=$(printf '%s' "${BODY}" | head -n1)
49+
if printf '%s' "${first_line}" | grep -Eq '^/ban(\s|$)'; then
50+
echo "match=true" >> "$GITHUB_OUTPUT"
51+
else
52+
echo "Comment starts with /ban prefix but is not the /ban command; skipping."
53+
echo "match=false" >> "$GITHUB_OUTPUT"
54+
fi
55+
56+
- name: Check commenter has admin permission on the repo
57+
if: steps.validate.outputs.match == 'true'
58+
env:
59+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60+
COMMENTER: ${{ github.event.comment.user.login }}
61+
REPO: ${{ github.repository }}
62+
run: |
63+
set -euo pipefail
64+
permission=$(gh api "repos/${REPO}/collaborators/${COMMENTER}/permission" --jq '.permission')
65+
echo "Commenter ${COMMENTER} has permission: ${permission}"
66+
if [[ "${permission}" != "admin" ]]; then
67+
echo "::error::/ban requires admin permission on the repo; ${COMMENTER} has '${permission}'."
68+
exit 1
69+
fi
70+
71+
- name: Mint GitHub App token
72+
if: steps.validate.outputs.match == 'true'
73+
id: app-token
74+
uses: actions/create-github-app-token@v1
75+
with:
76+
app-id: ${{ secrets.BAN_APP_ID }}
77+
private-key: ${{ secrets.BAN_APP_PRIVATE_KEY }}
78+
owner: ${{ github.repository_owner }}
79+
80+
- name: Block PR author from the org
81+
if: steps.validate.outputs.match == 'true'
82+
id: block
83+
env:
84+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
85+
ORG: ${{ github.repository_owner }}
86+
TARGET: ${{ github.event.issue.user.login }}
87+
ACTOR: ${{ github.event.comment.user.login }}
88+
run: |
89+
set -euo pipefail
90+
91+
if [[ "${TARGET}" == "${ACTOR}" ]]; then
92+
echo "::error::Refusing to self-ban (${ACTOR})."
93+
exit 1
94+
fi
95+
96+
target_role=$(gh api "orgs/${ORG}/memberships/${TARGET}" --jq '.role' 2>/dev/null || echo "not_a_member")
97+
if [[ "${target_role}" == "admin" ]]; then
98+
echo "::error::Refusing to ban ${TARGET}: they are an org admin."
99+
exit 1
100+
fi
101+
102+
echo "Blocking ${TARGET} from ${ORG} (requested by ${ACTOR})..."
103+
gh api --method PUT "orgs/${ORG}/blocks/${TARGET}"
104+
echo "Blocked ${TARGET}."
105+
106+
- name: React to the /ban comment
107+
if: always() && steps.validate.outputs.match == 'true'
108+
env:
109+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
110+
REPO: ${{ github.repository }}
111+
COMMENT_ID: ${{ github.event.comment.id }}
112+
run: |
113+
set -euo pipefail
114+
reaction='-1'
115+
if [[ "${{ steps.block.outcome }}" == "success" ]]; then
116+
reaction='+1'
117+
fi
118+
gh api --method POST \
119+
"repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \
120+
-f "content=${reaction}" >/dev/null
121+
122+
- name: Post outcome comment on the PR
123+
if: always() && steps.validate.outputs.match == 'true'
124+
env:
125+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
126+
REPO: ${{ github.repository }}
127+
PR: ${{ github.event.issue.number }}
128+
TARGET: ${{ github.event.issue.user.login }}
129+
ACTOR: ${{ github.event.comment.user.login }}
130+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
131+
run: |
132+
set -euo pipefail
133+
if [[ "${{ steps.block.outcome }}" == "success" ]]; then
134+
body=":no_entry: @${TARGET} has been blocked from the ${{ github.repository_owner }} org by @${ACTOR}."
135+
else
136+
body=":warning: \`/ban\` invoked by @${ACTOR} did not complete. See [workflow run](${RUN_URL})."
137+
fi
138+
gh api --method POST \
139+
"repos/${REPO}/issues/${PR}/comments" \
140+
-f "body=${body}" >/dev/null

.github/workflows/org-level-trivy-scan.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ jobs:
2020
contents: read
2121
packages: read
2222
security-events: write
23-
actions: read
2423

2524
env:
2625
SLACK_WEBHOOK_URL_SET: ${{ secrets.TEAM_SLACK_WEBHOOK_URL != '' }}

0 commit comments

Comments
 (0)