Skip to content

Commit be68241

Browse files
authored
fix: harden the workflows — spoofable actor check and overly broad permissions (#95)
1 parent 58ebaf2 commit be68241

9 files changed

Lines changed: 264 additions & 48 deletions

File tree

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ jobs:
2121
steps:
2222
- name: Checkout repository
2323
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24+
with:
25+
persist-credentials: false
2426

2527
- name: Initialize CodeQL
2628
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7

.github/workflows/hugo.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ on:
55
branches: ["main"]
66
workflow_dispatch:
77

8-
permissions:
9-
contents: read
10-
pages: write
11-
id-token: write
8+
# Nothing by default. pages: write and id-token: write belong to the deploy
9+
# job alone; at the top they were also handed to the build job, which only
10+
# needs to read the checkout.
11+
permissions: {}
1212

1313
concurrency:
1414
group: "pages"
@@ -21,13 +21,16 @@ defaults:
2121
jobs:
2222
build:
2323
runs-on: ubuntu-latest
24+
permissions:
25+
contents: read
2426
env:
2527
HUGO_VERSION: 0.165.0
2628
steps:
2729
- name: Checkout
2830
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2931
with:
3032
fetch-depth: 0
33+
persist-credentials: false
3134

3235
- name: Setup Go
3336
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
@@ -49,11 +52,14 @@ jobs:
4952
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
5053
HUGO_ENVIRONMENT: production
5154
TZ: Europe/Amsterdam
55+
# Through env rather than straight into the script: an expression
56+
# interpolated into run: is expanded before bash ever sees it.
57+
BASE_URL: ${{ steps.pages.outputs.base_url }}
5258
run: |
5359
cd src && hugo \
5460
--gc \
5561
--minify \
56-
--baseURL "${{ steps.pages.outputs.base_url }}/"
62+
--baseURL "${BASE_URL}/"
5763
5864
- name: Upload artifact
5965
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
@@ -65,6 +71,9 @@ jobs:
6571
name: github-pages
6672
url: ${{ steps.deployment.outputs.page_url }}
6773
runs-on: ubuntu-latest
74+
permissions:
75+
pages: write
76+
id-token: write
6877
needs: build
6978
steps:
7079
- name: Deploy to GitHub Pages

.github/workflows/pr-checks.yml

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ on:
44
pull_request:
55
branches: [main, development]
66

7-
permissions:
8-
contents: read
9-
pull-requests: write
7+
# Nothing by default; each job asks for exactly what it needs. Granting
8+
# pull-requests: write at the top handed it to every job, including the ones
9+
# that only read the checkout.
10+
permissions: {}
1011

1112
jobs:
1213

1314
# ── 1. PR title follows conventional commits ────────────────────────────────
1415
pr-title:
1516
name: Conventional commit title
1617
runs-on: ubuntu-latest
18+
permissions:
19+
pull-requests: read
1720
steps:
1821
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
1922
env:
@@ -33,8 +36,12 @@ jobs:
3336
markdown:
3437
name: Markdown lint
3538
runs-on: ubuntu-latest
39+
permissions:
40+
contents: read
3641
steps:
3742
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
43+
with:
44+
persist-credentials: false
3845
- uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
3946
with:
4047
globs: "src/content/**/*.md"
@@ -43,8 +50,12 @@ jobs:
4350
python-security:
4451
name: Python security (bandit)
4552
runs-on: ubuntu-latest
53+
permissions:
54+
contents: read
4655
steps:
4756
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57+
with:
58+
persist-credentials: false
4859
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
4960
with:
5061
python-version: "3.x"
@@ -56,8 +67,14 @@ jobs:
5667
image-format:
5768
name: No PNG/JPG in static/images
5869
runs-on: ubuntu-latest
70+
# pull-requests: write for the comment this job posts when it finds a PNG.
71+
permissions:
72+
contents: read
73+
pull-requests: write
5974
steps:
6075
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
76+
with:
77+
persist-credentials: false
6178

6279
- name: Find non-AVIF images
6380
id: check
@@ -127,8 +144,12 @@ jobs:
127144
bilingual:
128145
name: EN/NL file parity
129146
runs-on: ubuntu-latest
147+
permissions:
148+
contents: read
130149
steps:
131150
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
151+
with:
152+
persist-credentials: false
132153
- name: Check every .md has a matching .nl.md
133154
run: |
134155
missing=""
@@ -151,12 +172,15 @@ jobs:
151172
hugo-build:
152173
name: Hugo build
153174
runs-on: ubuntu-latest
175+
permissions:
176+
contents: read
154177
env:
155178
HUGO_VERSION: 0.165.0
156179
steps:
157180
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
158181
with:
159182
fetch-depth: 0
183+
persist-credentials: false
160184
- name: Setup Go
161185
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
162186
with:
@@ -183,6 +207,8 @@ jobs:
183207
link-check:
184208
name: Broken link check
185209
runs-on: ubuntu-latest
210+
permissions:
211+
contents: read
186212
needs: hugo-build
187213
steps:
188214
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -231,6 +257,9 @@ jobs:
231257
update-checklist:
232258
name: Update PR checklist
233259
runs-on: ubuntu-latest
260+
# pull-requests: write to rewrite the description's checklist.
261+
permissions:
262+
pull-requests: write
234263
if: always()
235264
needs: [pr-title, bilingual, image-format, hugo-build, link-check]
236265
steps:

.github/workflows/python-checks.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ on:
99
- cron: '0 5 * * 0'
1010
workflow_dispatch:
1111

12+
# Without this the workflow inherits whatever the repository default is.
13+
permissions: {}
14+
1215
jobs:
1316
lint:
1417
runs-on: ubuntu-latest
18+
permissions:
19+
contents: read
1520
steps:
1621
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22+
with:
23+
persist-credentials: false
1724

1825
- name: Set up Python
1926
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0

.github/workflows/trivy-scan.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ jobs:
1414
runs-on: ubuntu-latest
1515
steps:
1616
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17+
with:
18+
persist-credentials: false
1719

1820
- name: Run Trivy filesystem scan
1921
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0

.github/workflows/update-checksums.yml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,27 @@ jobs:
2828
runs-on: ubuntu-latest
2929
# Only Renovate's own branches. Running this on a human's pull request
3030
# would mean pushing commits to a branch someone is actively working on.
31-
if: startsWith(github.head_ref, 'renovate/') && github.actor == 'renovate[bot]'
31+
#
32+
# The author of the pull request, not github.actor. actor is whoever
33+
# triggered the most recent event, which on a synchronize is whoever pushed
34+
# last; comparing that to a bot name is a check zizmor rightly calls
35+
# spoofable. The author is fixed when the pull request is opened and cannot
36+
# be set to another account.
37+
if: >-
38+
startsWith(github.head_ref, 'renovate/') &&
39+
github.event.pull_request.user.login == 'renovate[bot]'
3240
permissions:
3341
contents: write
3442
steps:
43+
# persist-credentials: false, even though this job pushes. Otherwise the
44+
# token sits in .git/config for the whole job, including while the script
45+
# below downloads release tarballs off the internet. The push step gets
46+
# the token explicitly instead, for exactly one command.
3547
- name: Check out the pull request branch
3648
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3749
with:
3850
ref: ${{ github.head_ref }}
51+
persist-credentials: false
3952

4053
# The script verifies each download against the checksum the project
4154
# publishes next to the release before writing anything, so a hash only
@@ -44,6 +57,9 @@ jobs:
4457
run: .github/scripts/update-tool-checksums.sh --apply
4558

4659
- name: Commit updated checksums
60+
env:
61+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
BRANCH: ${{ github.head_ref }}
4763
run: |
4864
git config user.name "github-actions[bot]"
4965
git config user.email "github-actions[bot]@users.noreply.github.com"
@@ -52,5 +68,6 @@ jobs:
5268
echo "Checksums are already up to date, nothing to commit."
5369
else
5470
git commit -m "chore: update tool SHA256 checksums"
55-
git push
71+
git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
72+
"HEAD:refs/heads/${BRANCH}"
5673
fi

src/content/docs/networking/eduroam-network-installation.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ The guide at [linux.datanose.nl](https://linux.datanose.nl/linux/eduroam/) (UvA/
3030

3131
## What does work
3232

33-
PEAP/MSCHAPv2 with CA validation via the system trust store and `domain-suffix-match` (the modern replacement for the deprecated `altsubject-matches`).
33+
PEAP/MSCHAPv2 validated against Saxion's own certificate authority, pinned inside the
34+
script, plus `domain-suffix-match` (the modern replacement for the deprecated
35+
`altsubject-matches`).
36+
37+
The script used to point at the system trust store, which meant any of the roughly 150
38+
public CAs your distribution ships could vouch for a server calling itself
39+
`ise.infra.saxion.net`. It now trusts only the chain Saxion publishes through eduroam
40+
CAT — USERTrust RSA Certification Authority and GEANT OV RSA CA 4 — which is what the
41+
official CAT installers do.
3442

3543
**Requirements:**
3644
- Python 3.10+
@@ -44,7 +52,7 @@ PEAP/MSCHAPv2 with CA validation via the system trust store and `domain-suffix-m
4452
| Authentication | Protected EAP (PEAP) |
4553
| PEAP version | Automatic |
4654
| Inner authentication | MSCHAPv2 |
47-
| CA certificate | System CA bundle (`/etc/pki/tls/certs/ca-bundle.crt`) |
55+
| CA certificate | Saxion's published chain, written to `~/.config/saxion-eduroam/saxion-eduroam-ca.pem` |
4856
| Domain validation | `domain-suffix-match: ise.infra.saxion.net` |
4957
| Phase2 domain validation | `phase2-domain-suffix-match: ise.infra.saxion.net` |
5058
| Anonymous identity | `anonymous@saxion.nl` |
@@ -59,13 +67,13 @@ A Python script automates the full `nmcli` connection setup for Saxion:
5967
curl -LO https://zephyrus-linux.stensel.nl/scripts/saxion-eduroam.py
6068

6169
# 2. Verify checksum
62-
echo "bef16a8ce91644a26cdd428f8dd0300de8e49ed72d9cbf4b6d39efea6d8facc1 saxion-eduroam.py" | sha256sum -c
70+
echo "b1a9b7ee4a55f77e118d40e886979ca96c8db8e145d593027f0bde0a578c46bc saxion-eduroam.py" | sha256sum -c
6371

6472
# 3. Run
6573
python3 saxion-eduroam.py
6674
```
6775

68-
**SHA256:** `bef16a8ce91644a26cdd428f8dd0300de8e49ed72d9cbf4b6d39efea6d8facc1`
76+
**SHA256:** `b1a9b7ee4a55f77e118d40e886979ca96c8db8e145d593027f0bde0a578c46bc`
6977

7078
The script removes any existing eduroam profile, prompts for your **username** via a GUI dialog (zenity, kdialog, or yad) or terminal fallback, and activates the connection. Your password is never asked by the script; it is requested by your GNOME Keyring at connection time and stored securely, never in plaintext.
7179

@@ -100,7 +108,7 @@ nmcli connection add \
100108
802-1x.identity "user@institution.tld" \
101109
802-1x.password "your-password" \
102110
802-1x.anonymous-identity "anonymous@saxion.nl" \
103-
802-1x.ca-cert file:///etc/pki/tls/certs/ca-bundle.crt \
111+
802-1x.ca-cert file://$HOME/.config/saxion-eduroam/saxion-eduroam-ca.pem \
104112
802-1x.domain-suffix-match "ise.infra.saxion.net" \
105113
802-1x.phase2-domain-suffix-match "ise.infra.saxion.net"
106114
```

src/content/docs/networking/eduroam-network-installation.nl.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ De handleiding op [linux.datanose.nl](https://linux.datanose.nl/linux/eduroam/)
3030

3131
## Wat wel werkt
3232

33-
PEAP/MSCHAPv2 met CA-validatie via de systeem-truststore en `domain-suffix-match` (de moderne vervanging voor het verouderde `altsubject-matches`).
33+
PEAP/MSCHAPv2, gevalideerd tegen Saxion's eigen certificaatautoriteit die in het script
34+
is vastgelegd, plus `domain-suffix-match` (de moderne vervanging voor het verouderde
35+
`altsubject-matches`).
36+
37+
Het script wees eerder naar de systeem-truststore. Daarmee kon elk van de ongeveer 150
38+
publieke CA's die je distributie meelevert instaan voor een server die zich
39+
`ise.infra.saxion.net` noemt. Nu wordt alleen de keten vertrouwd die Saxion via eduroam
40+
CAT publiceert — USERTrust RSA Certification Authority en GEANT OV RSA CA 4 — precies
41+
wat de officiële CAT-installers doen.
3442

3543
**Vereisten:**
3644
- Python 3.10+
@@ -44,7 +52,7 @@ PEAP/MSCHAPv2 met CA-validatie via de systeem-truststore en `domain-suffix-match
4452
| Authenticatie | Protected EAP (PEAP) |
4553
| PEAP-versie | Automatisch |
4654
| Interne authenticatie | MSCHAPv2 |
47-
| CA-certificaat | Systeem-CA-bundel (`/etc/pki/tls/certs/ca-bundle.crt`) |
55+
| CA-certificaat | De door Saxion gepubliceerde keten, geschreven naar `~/.config/saxion-eduroam/saxion-eduroam-ca.pem` |
4856
| Domeinvalidatie | `domain-suffix-match: ise.infra.saxion.net` |
4957
| Fase-2-domeinvalidatie | `phase2-domain-suffix-match: ise.infra.saxion.net` |
5058
| Anonieme identiteit | `anonymous@saxion.nl` |
@@ -59,13 +67,13 @@ Een Python-script automatiseert de volledige `nmcli`-verbindingsconfiguratie voo
5967
curl -LO https://zephyrus-linux.stensel.nl/scripts/saxion-eduroam.py
6068

6169
# 2. Controleer de checksum
62-
echo "bef16a8ce91644a26cdd428f8dd0300de8e49ed72d9cbf4b6d39efea6d8facc1 saxion-eduroam.py" | sha256sum -c
70+
echo "b1a9b7ee4a55f77e118d40e886979ca96c8db8e145d593027f0bde0a578c46bc saxion-eduroam.py" | sha256sum -c
6371

6472
# 3. Uitvoeren
6573
python3 saxion-eduroam.py
6674
```
6775

68-
**SHA256:** `bef16a8ce91644a26cdd428f8dd0300de8e49ed72d9cbf4b6d39efea6d8facc1`
76+
**SHA256:** `b1a9b7ee4a55f77e118d40e886979ca96c8db8e145d593027f0bde0a578c46bc`
6977

7078
Het script verwijdert een eventueel bestaand eduroam-profiel, vraagt je **gebruikersnaam** via een GUI-dialoog (zenity, kdialog of yad) of terminal-fallback, en activeert de verbinding. Je wachtwoord wordt nooit door het script gevraagd; dat wordt bij het verbinden opgevraagd door je GNOME Keyring en veilig opgeslagen, nooit in platte tekst.
7179

@@ -100,7 +108,7 @@ nmcli connection add \
100108
802-1x.identity "gebruiker@instelling.nl" \
101109
802-1x.password "je-wachtwoord" \
102110
802-1x.anonymous-identity "anonymous@saxion.nl" \
103-
802-1x.ca-cert file:///etc/pki/tls/certs/ca-bundle.crt \
111+
802-1x.ca-cert file://$HOME/.config/saxion-eduroam/saxion-eduroam-ca.pem \
104112
802-1x.domain-suffix-match "ise.infra.saxion.net" \
105113
802-1x.phase2-domain-suffix-match "ise.infra.saxion.net"
106114
```

0 commit comments

Comments
 (0)