Skip to content
63 changes: 63 additions & 0 deletions .github/workflows/ansible-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
name: Ansible Lint

# Actions are pinned to full commit SHAs (the trailing `# vX.Y.Z` is the tag
# that SHA corresponded to) and every tool to an exact version -- no floating
# tags. Version bumps are therefore explicit, reviewable changes to this file.
#
# Only GitHub-hosted runners and public actions are used.
#
# Soft-fail for the initial rollout: findings publish to Security -> Code
# scanning instead of blocking the branch. Tighten the `exit-code` /
# `--soft-fail` / `continue-on-error` switches once the backlog is triaged.

on:
workflow_dispatch:
push:
branches:
- main
- release-1.0
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
ansible-lint:
name: Ansible Lint
runs-on: ubuntu-24.04
steps:
- name: Harden the runner (audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit

- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12.14'

- name: Install ansible-lint
run: pip install --disable-pip-version-check "ansible-lint==26.8.0"

# Style-only rule families are skipped: unfiltered they produced 501
# findings here, of which 6 were substantive. name[casing] alone
# accounted for 350 of them, fighting this repo's deliberate
# `role | action | desc` naming convention. What is left is correctness
# and command-shell risk.
- name: Run ansible-lint
continue-on-error: true
run: >-
ansible-lint --nocolor
--skip-list name,var-naming,fqcn,yaml,jinja,key-order
roles/
94 changes: 94 additions & 0 deletions .github/workflows/bandit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
name: Bandit

# Actions are pinned to full commit SHAs (the trailing `# vX.Y.Z` is the tag
# that SHA corresponded to) and every tool to an exact version -- no floating
# tags. Version bumps are therefore explicit, reviewable changes to this file.
#
# Only GitHub-hosted runners and public actions are used.
#
# Soft-fail for the initial rollout: findings publish to Security -> Code
# scanning instead of blocking the branch. Tighten the `exit-code` /
# `--soft-fail` / `continue-on-error` switches once the backlog is triaged.

on:
workflow_dispatch:
push:
branches:
- main
- release-1.0
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
schedule:
# Weekly re-scan so new findings surface without a code change.
# NOTE: GitHub only fires `schedule` on the repository's default
# branch, so this trigger stays dormant until these workflows are
# also merged to `main`. The push and pull_request triggers above
# work on every listed branch regardless.
- cron: '20 4 * * 1'

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
bandit:
name: Bandit (Python security)
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
steps:
- name: Harden the runner (audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit

- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12.14'

- name: Install Bandit
run: >-
pip install --disable-pip-version-check
"bandit[sarif,toml]==1.9.4"

# Skipped tests, and why each one cannot be true here:
# B101 assert_used -- asserts in test code
# B601 paramiko_calls -- paramiko is not a dependency
# B404 import_subprocess -- importing subprocess is not a defect
# B603 subprocess_without_shell -- fires on the *recommended* argv-list
# form and cannot judge whether the input is trusted; the genuinely
# dangerous variants (B602/B604/B605/B609, shell=True and friends)
# stay enabled.
# Severity is deliberately not filtered: several real tests (e.g. B105
# hardcoded password) report LOW, so raising the floor would hide them.
# Scanned once. The readable summary below is rendered from the SARIF
# that scan produced rather than by scanning the tree a second time.
- name: Run Bandit
run: |
bandit -r . -x ./venv,./.venv,./tests -s B101,B404,B601,B603 \
-f sarif -o bandit-results.sarif --exit-zero
jq -r '"Bandit: \(.runs[0].results | length) finding(s)",
(.runs[0].results[]
| " \(.level)\t\(.ruleId)\t"
+ "\(.locations[0].physicalLocation.artifactLocation.uri)"
+ ":\(.locations[0].physicalLocation.region.startLine)\t"
+ "\(.message.text | split("\n")[0])")' \
bandit-results.sarif

- name: Upload Bandit SARIF
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
if: always() && hashFiles('bandit-results.sarif') != ''
with:
sarif_file: bandit-results.sarif
category: bandit
87 changes: 87 additions & 0 deletions .github/workflows/checkov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
name: Checkov

# Actions are pinned to full commit SHAs (the trailing `# vX.Y.Z` is the tag
# that SHA corresponded to) and every tool to an exact version -- no floating
# tags. Version bumps are therefore explicit, reviewable changes to this file.
#
# Only GitHub-hosted runners and public actions are used.
#
# Soft-fail for the initial rollout: findings publish to Security -> Code
# scanning instead of blocking the branch. Tighten the `exit-code` /
# `--soft-fail` / `continue-on-error` switches once the backlog is triaged.

on:
workflow_dispatch:
push:
branches:
- main
- release-1.0
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
schedule:
# Weekly re-scan so new findings surface without a code change.
# NOTE: GitHub only fires `schedule` on the repository's default
# branch, so this trigger stays dormant until these workflows are
# also merged to `main`. The push and pull_request triggers above
# work on every listed branch regardless.
- cron: '40 4 * * 1'

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
checkov:
name: Checkov (Kubernetes / Actions / secrets)
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
steps:
- name: Harden the runner (audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit

- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12.14'

- name: Install Checkov
run: pip install --disable-pip-version-check "checkov==3.3.16"

# `helm` is deliberately not in --framework: Checkov renders charts into a
# random temp dir and reports paths relative to it, so every finding points
# at a file that does not exist in the repo -- unanchorable in the Security
# tab, and re-reported as new on every run.
#
# charts/ is skipped for the same reason the Trivy workflow skips it: it
# is vendored upstream (Istio, LiteLLM, valkey) and its defaults are not
# ours to change here. Neither tool reports on it, deliberately -- a
# decision to revisit if the charts are ever forked rather than vendored.
- name: Run Checkov
continue-on-error: true
run: |
checkov -d . \
--framework kubernetes github_actions secrets \
--skip-path 'charts/' \
--soft-fail --compact --quiet \
-o sarif --output-file-path console,. || true
ls -la results.sarif 2>/dev/null || echo "no results.sarif produced"

- name: Upload Checkov SARIF
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
if: always() && hashFiles('results.sarif') != ''
with:
sarif_file: results.sarif
category: checkov
66 changes: 66 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
name: Scorecard supply-chain security

# Actions are pinned to full commit SHAs (the trailing `# vX.Y.Z` is the tag
# that SHA corresponded to) and every tool to an exact version -- no floating
# tags. Version bumps are therefore explicit, reviewable changes to this file.
#
# Only GitHub-hosted runners and public actions are used.
#
# OpenSSF Scorecard rates repository security posture (branch protection,
# pinned dependencies, token permissions, ...). Results publish to Security ->
# Code scanning. Runs on the default branch and on a schedule -- Scorecard
# grades the repository, not an individual pull request.

on:
workflow_dispatch:
branch_protection_rule:
push:
branches:
- main
schedule:
# Like every `schedule` trigger, this only fires once the workflow is
# present on the default branch; it is dormant on any other branch.
- cron: '45 23 * * 5'

permissions: read-all

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
id-token: write
steps:
- name: Harden the runner (audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit

- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Run analysis
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: scorecard-results.sarif
results_format: sarif
publish_results: true

- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: scorecard-results
path: scorecard-results.sarif
retention-days: 5

- name: Upload Scorecard SARIF
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
if: always() && hashFiles('scorecard-results.sarif') != ''
with:
sarif_file: scorecard-results.sarif
category: scorecard
Loading
Loading