Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
# Dependabot — keeps the SHA-pinned GitHub Actions current (it rewrites
# `uses: owner/repo@<sha> # vX.Y.Z` to the new sha + version comment).
#
# github-actions is the only applicable ecosystem: the repo has no pip/npm/etc.
# manifests, and `ansible/requirements.yml` is Ansible Galaxy, which Dependabot
# does not support.
#
# NOT covered here, bump manually:
# - the KICS engine image digest in the Makefile — see CONTRIBUTING.md
# - the Galaxy collections in ansible/requirements.yml
# - pre-commit hook revs — run `pre-commit autoupdate`
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
commit-message:
prefix: ci # conventional commits -> "ci(deps): ..."
include: scope
groups:
actions:
patterns: ["*"]
ignore:
# Pinned to the post-remediation hardened HEAD (see ci.yml). The newest
# RELEASE tag (v2.1.20) points at an older commit that predates the April
# 2026 base-image digest-pinning, so an automated bump would DOWNGRADE
# security. Re-pin manually only after verifying a newer clean commit/tag.
- dependency-name: "Checkmarx/kics-github-action"
150 changes: 150 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
name: CI

on:
push:
branches: [main]
pull_request:

# Least privilege by default; jobs widen only what they need.
permissions:
contents: read

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

# Third-party actions are pinned to a full commit SHA (a re-pointed tag can ship
# malicious code — cf. the March 2026 KICS action compromise). The trailing
# comment records the human-readable version; .github/dependabot.yml bumps them.
jobs:
# Detect which units changed so the heavy Ansible jobs skip bash-only PRs.
changes:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
ansible: ${{ steps.filter.outputs.ansible }}
services: ${{ steps.filter.outputs.services }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
ansible:
- 'ansible/**'
services:
- 'services/**'

# Fast, repo-wide gate: hygiene, shellcheck, yamllint, markdown.
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1

# Ansible style + best-practice + the production-profile SECURITY rules,
# plus a syntax-check of every playbook. Runs only when ansible/ changed.
ansible-lint:
needs: changes
if: needs.changes.outputs.ansible == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: ansible
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install Ansible tooling
run: python -m pip install --upgrade ansible ansible-lint yamllint
- name: Install Galaxy collections
run: make deps # must precede lint/syntax-check
- name: Lint (yamllint + ansible-lint)
run: make lint
- name: Syntax-check playbooks
# Dummy inventory: the real inventory/hosts.yml is git-ignored, and
# --syntax-check only parses, it never connects.
run: |
for p in playbooks/site.yml playbooks/anvil.yml playbooks/add-dev-user.yml; do
echo "::group::syntax-check $p"
ansible-playbook "$p" --syntax-check -i localhost,
echo "::endgroup::"
done

# Dedicated IaC security scan of the Ansible tree via the official KICS action.
# KICS severities are HIGH/MEDIUM/LOW/INFO (no "critical"); we gate on HIGH.
#
# SUPPLY-CHAIN NOTE: this action's git tags were hijacked in the March 2026
# TeamPCP attack (CISA KEV). It has since been remediated — tags restored to
# their legitimate pre-hijack commits and explicit hardening applied (base
# images digest-pinned, workflows SHA-pinned, StepSecurity best practices). We
# pin to the post-remediation hardened HEAD by SHA; the `v2.1.20` *tag* points
# at the older Mar-04 commit that predates the April base-image digest-pinning,
# so we deliberately do NOT use the tag (and Dependabot is told not to bump it
# — see .github/dependabot.yml). Re-verify the SHA before any change.
kics:
needs: changes
if: needs.changes.outputs.ansible == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: KICS Ansible security scan (fail on HIGH)
# master @ 2026-05-22 "[StepSecurity] Apply security best practices (#157)"
uses: Checkmarx/kics-github-action@7117906d8779ecaf5180f34c4931a774f10d7625
with:
path: ansible
platform_type: Ansible
exclude_paths: ansible/collections
fail_on: high
output_formats: json,sarif
output_path: kics-results
enable_jobs_summary: true
- name: Upload KICS results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: kics-results
path: kics-results/
if-no-files-found: ignore
# If GitHub Advanced Security is enabled on this private repo, surface KICS
# findings in the Security tab by un-commenting the block below (add
# `security-events: write` to this job's permissions):
# - name: Upload SARIF to code scanning
# if: always()
# uses: github/codeql-action/upload-sarif@d77b13a0df3134d64a457ea9003f600b09fa1c8a # v3.36.1
# with:
# sarif_file: kics-results/results.sarif

# Lint the workflow files themselves.
actionlint:
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0
with:
reporter: github-check
fail_on_error: true

# Solidity (forge fmt + build) self-activates once real .sol sources land:
# solidity:
# needs: changes
# if: needs.changes.outputs.services == 'true' && hashFiles('services/anvil-devnet/contracts/src/**/*.sol') != ''
# runs-on: ubuntu-latest
# defaults:
# run:
# working-directory: services/anvil-devnet/contracts
# steps:
# - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# - uses: foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d # v1.8.0
# - run: forge fmt --check
# - run: forge build --sizes
39 changes: 39 additions & 0 deletions .github/workflows/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: Molecule

# Containerised converge + idempotence + verify for the anvil/caddy roles.
# Heavy (privileged systemd Docker container) — scoped to ansible/ changes and
# blocking. Mark it a required status check in branch protection once proven.
on:
pull_request:
paths: ['ansible/**']
push:
branches: [main]
paths: ['ansible/**']

permissions:
contents: read

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

jobs:
molecule:
runs-on: ubuntu-latest # Docker is preinstalled
defaults:
run:
working-directory: ansible
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install molecule + Ansible
run: |
python -m pip install --upgrade \
molecule "molecule-plugins[docker]" ansible ansible-lint docker
- name: Install Galaxy collections
run: make deps
- name: molecule test
run: molecule test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@

# Local, per-developer Claude Code settings (not shared)
.claude/settings.local.json

# KICS security-scan output (`make security`; CI uploads it as an artifact)
kics-results/
22 changes: 22 additions & 0 deletions .markdownlint-cli2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# markdownlint-cli2 config — relaxed ruleset so the existing docs pass while
# still catching genuine structural issues (broken links, empty/duplicate
# headings, multiple H1s, …) in new Markdown. The disabled rules below are
# purely stylistic or fire pervasively on the current docs; re-enable and fix
# incrementally if the team wants stricter formatting.
config:
MD013: false # line length — prose/tables run long here
MD033: false # inline HTML — allowed in docs
MD041: false # first line need not be a top-level heading
MD040: false # bare code fences (ASCII trees) are fine
MD031: false # blank lines around fences — pervasive in docs
MD022: false # blank lines around headings — pervasive in docs
MD049: false # emphasis style (underscore vs asterisk) — stylistic
MD060: false # table pipe spacing/alignment — finicky, opinionated
MD004: false # unordered list marker style (-, +, *) — stylistic
MD024:
siblings_only: true # duplicate headings only flagged within a section

# Vendored collection docs are excluded at the pre-commit layer too.
ignores:
- "ansible/collections/**"
- "**/node_modules/**"
89 changes: 89 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
# Pre-commit hooks for the deCDN DevOps monorepo.
#
# pip install pre-commit && make hooks # one-time install
# make lint # run on all files
#
# Heavier Ansible checks (ansible-lint, syntax-check, KICS security scan,
# molecule) run in CI only — see .github/workflows/. ansible-lint's production
# profile already carries the Ansible security rules; this file is the fast
# local gate (hygiene, shellcheck, yamllint, markdown).
minimum_pre_commit_version: "3.5.0"
default_install_hook_types: [pre-commit]

# Vendored / generated trees only.
exclude: >-
(?x)^(
ansible/collections/|
ansible/\.ansible/|
services/anvil-devnet/contracts/(out|cache|broadcast)/
)

repos:
# ── Generic hygiene ────────────────────────────────────────────────────────
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md] # keep markdown hard line breaks
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-added-large-files
args: [--maxkb=512]
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
# Jinja2 templates render files that carry a shebang (e.g. nftables.conf
# starts with `#!/usr/sbin/nft -f`); the template itself isn't a script.
exclude: \.j2$
- id: mixed-line-ending
args: [--fix=lf]
- id: check-yaml
args: [--unsafe] # tolerate custom/!vault tags; syntax check only
- id: check-json
- id: check-toml

# ── Bash ───────────────────────────────────────────────────────────────────
# Honors inline `# shellcheck` directives and the repo .shellcheckrc.
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.11.0
hooks:
- id: shellcheck

# ── Markdown (relaxed ruleset; see .markdownlint-cli2.yaml) ────────────────
- repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.22.1
hooks:
- id: markdownlint-cli2

# ── YAML lint for the Ansible tree (reuses ansible/.yamllint) ──────────────
# Scoped to ansible/ so services/ YAML isn't held to Ansible style, and so it
# stays in lockstep with `make -C ansible lint`. *.yml.example templates don't
# match (they end in .example) and are skipped.
- repo: local
hooks:
- id: yamllint-ansible
name: yamllint (ansible/)
entry: yamllint -c ansible/.yamllint
language: python
additional_dependencies: ["yamllint==1.35.1"]
files: ^ansible/.*\.(ya?ml)$
exclude: ^ansible/(collections|\.ansible)/

# Opt-in: full ansible-lint locally (needs `make -C ansible deps` first).
# pre-commit run ansible-lint --hook-stage manual
- id: ansible-lint
name: ansible-lint (manual — run `make -C ansible deps` first)
entry: bash -c 'make -C ansible lint'
language: system
pass_filenames: false
stages: [manual]

# Solidity (forge fmt) self-activates once contracts/src has real .sol files:
# - repo: local
# hooks:
# - id: forge-fmt
# name: forge fmt --check
# entry: bash -c 'cd services/anvil-devnet/contracts && forge fmt --check'
# language: system
# files: ^services/anvil-devnet/contracts/.*\.sol$
# exclude: \.example$
7 changes: 7 additions & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Repo-wide ShellCheck settings (read by the shellcheck pre-commit hook and CI).
#
# SC1091: the ops scripts `source ./lib.sh` at runtime relative to their own
# install dir; that file isn't resolvable during a static lint from the repo
# root, and following it isn't needed to check the callers. lib.sh itself is
# still linted directly.
disable=SC1091
Loading
Loading