Skip to content

Commit 2e2a854

Browse files
authored
Add conftest policies for GitHub Actions (#5532)
- Add conftest workflow and policy rules (see .github/conftest/README.md) - Add pinned-actions rule to enforce SHA pinning on external actions NO_CHANGELOG=true
1 parent 9f554cc commit 2e2a854

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

.github/conftest/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Conftest policies for GitHub Actions
2+
3+
This directory contains [Conftest](https://www.conftest.dev/) policies that
4+
validate GitHub Actions [workflows] and [composite actions]. They are evaluated
5+
by the [conftest workflow](../workflows/conftest.yml) on every push and pull
6+
request that touches `.github/`.
7+
8+
## Adding a new rule
9+
10+
1. Create a new `.rego` file under `rules/`.
11+
2. Use `package main` and add violations to `deny`.
12+
3. Include a comment block at the top of the file explaining the rule and how
13+
to fix violations.
14+
4. Push — the conftest workflow picks up new rules automatically.
15+
16+
Note that workflows and composite actions have different YAML schemas.
17+
Workflows define jobs under `jobs.<name>.steps`, while composite actions define
18+
steps under `runs.steps`. Rules that inspect steps must handle both.
19+
20+
## Running locally
21+
22+
```bash
23+
# Install conftest (macOS)
24+
brew install conftest
25+
26+
# Run all policies against workflows and composite actions
27+
conftest test \
28+
.github/workflows/*.yml \
29+
.github/actions/*/action.yml \
30+
--policy .github/conftest/rules
31+
```
32+
33+
## References
34+
35+
- [Conftest](https://www.conftest.dev/) — policy testing tool for configuration files
36+
- [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) — the policy language used by Conftest and OPA
37+
- [Workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) — YAML schema for `.github/workflows/*.yml`
38+
- [Composite actions](https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action) — YAML schema for `action.yml` in composite actions
39+
- [Security hardening](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions) — GitHub's guide to securing workflows
40+
- [Using third-party actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions) — why pinning to commit SHAs matters
41+
42+
[workflows]: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions
43+
[composite actions]: https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Action pinning — supply-chain protection
2+
#
3+
# External actions must be pinned to a full 40-character commit SHA.
4+
# Mutable tags like @v1 can be reassigned to point at malicious commits.
5+
# Local composite actions (./...) are exempt.
6+
#
7+
# Good: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
8+
# Bad: actions/checkout@v4
9+
# Bad: actions/checkout@main
10+
#
11+
# How to fix:
12+
# 1. Find the tag you want to pin (e.g. v4.3.1).
13+
# 2. Look up the commit SHA:
14+
# git ls-remote --tags https://github.com/<owner>/<action>.git '<tag>^{}' '<tag>'
15+
# 3. Replace the tag with the SHA and add a comment with the tag name:
16+
# uses: actions/checkout@<sha> # v4.3.1
17+
#
18+
# Always include the "# <tag>" suffix comment so humans can tell which
19+
# version is pinned. This cannot be enforced by conftest (YAML strips
20+
# comments during parsing), so it is a convention to follow manually.
21+
22+
package main
23+
24+
import rego.v1
25+
26+
_is_pinned(ref) if {
27+
regex.match(`^[^@]+@[0-9a-f]{40}$`, ref)
28+
}
29+
30+
_is_local(ref) if {
31+
startswith(ref, "./")
32+
}
33+
34+
# Workflow files: jobs.<name>.steps[].uses
35+
deny contains msg if {
36+
some job_name, job in input.jobs
37+
some i, step in job.steps
38+
step.uses
39+
not _is_local(step.uses)
40+
not _is_pinned(step.uses)
41+
msg := sprintf("%s: step %d: action '%s' must be pinned to a full commit SHA", [job_name, i, step.uses])
42+
}
43+
44+
# Composite actions: runs.steps[].uses
45+
deny contains msg if {
46+
some i, step in input.runs.steps
47+
step.uses
48+
not _is_local(step.uses)
49+
not _is_pinned(step.uses)
50+
msg := sprintf("step %d: action '%s' must be pinned to a full commit SHA", [i, step.uses])
51+
}

.github/workflows/conftest.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: conftest
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- '.github/**'
8+
pull_request:
9+
paths:
10+
- '.github/**'
11+
12+
env:
13+
CONFTEST_VERSION: "0.67.0"
14+
CONFTEST_SHA256: "a98cfd236f3cadee16d860fbe31cc6edcb0da3efc317f661c7ccd097164b1fdf"
15+
16+
jobs:
17+
conftest:
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23+
24+
- name: Install conftest
25+
run: |-
26+
curl -fsSL "https://github.com/open-policy-agent/conftest/releases/download/v${CONFTEST_VERSION}/conftest_${CONFTEST_VERSION}_Linux_x86_64.tar.gz" \
27+
-o conftest.tar.gz
28+
echo "${CONFTEST_SHA256} conftest.tar.gz" | sha256sum --check --strict
29+
tar xz -f conftest.tar.gz conftest
30+
sudo mv conftest /usr/local/bin/
31+
32+
- name: Run conftest
33+
shell: bash
34+
run: |-
35+
shopt -s nullglob
36+
conftest test \
37+
.github/workflows/*.yml \
38+
.github/workflows/*.yaml \
39+
.github/actions/*/action.yml \
40+
.github/actions/*/action.yaml \
41+
--policy .github/conftest/rules

0 commit comments

Comments
 (0)