CLI + GitHub Action: fail CI when required systemd .service units in your repo exceed a security exposure threshold, using systemd-analyze security in offline mode.
- Finds
.servicefiles by glob(s) - Builds a temporary
--rootlayout and runs:systemd-analyze security --offline=yes --root=... --threshold=... <unit>systemd-analyze security --offline=yes --root=... --json=short <unit>(for reports)
- Produces:
- Markdown summary (stdout +
$GITHUB_STEP_SUMMARYif set) - Optional JSON report
- Optional SARIF report (for GitHub Code Scanning upload)
- Markdown summary (stdout +
systemd-analyzev250+ (offline mode +--json=short+--security-policy+--threshold)- v1 scope: only
.serviceunits (no.socket/.timermapping yet)
Build:
go build ./cmd/ssgScan:
./ssg scan \
--repo-root . \
--paths 'deploy/systemd/**/*.service' \
--threshold 6.0 \
--policy .ci/systemd-security-policy.json \
--allowlist .ci/ssg-allowlist.json \
--json-report ssg.json \
--sarif-report ssg.sarifModes:
--mode enforce(default): exit non-zero if any unit fails and is not allowlisted--mode report: never fail on threshold checks (adoption mode), but still fails on analysis errors
--allowlist <path> points to a JSON file:
{
"allowUnits": [
"deploy/systemd/legacy.service",
"legacy.service"
],
"allowTests": [
{ "unit": "deploy/systemd/myapp.service", "test": "PrivateNetwork" },
{ "unit": "myapp.service", "test": "ProtectSystem" }
]
}Notes:
unitmay be either repo-relative path or just the unit filename.testshould matchjson_field/namefromsystemd-analyze security --json=short.- Current semantics: if a unit exceeds
--threshold, it is treated as allowed if:- the unit is in
allowUnits, or - all non-zero-exposure checks are listed in
allowTestsfor that unit.
- the unit is in
This repo includes a container action at repo root (action.yml + Dockerfile). To use it, publish it to GitHub and reference it in workflows.
Example workflow:
name: systemd security gate
on:
pull_request:
push:
branches: [ main ]
jobs:
ssg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: teunlao/systemd-security-gate@v0
with:
paths: |
deploy/systemd/**/*.service
threshold: "6.0"
policy: .ci/systemd-security-policy.json
allowlist: .ci/ssg-allowlist.json
json_report: ssg.json
sarif_report: ssg.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ssg.sarifMIT (see LICENSE).
GitHub Actions should be pinned to a tag (or SHA) instead of main.
Recommended:
- use
@v0for “latest stable v0.x” - use
@v0.x.yfor fully reproducible builds
Example:
- uses: teunlao/systemd-security-gate@v0