diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..17b9f12 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab + +[*.{json,md,yaml,yml}] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..11c93a6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +* text=auto eol=lf + +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +*.gif binary +*.ico binary +*.jpg binary +*.jpeg binary +*.png binary +*.pdf binary diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9192c6e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + github-actions: + patterns: + - "*" + + - package-ecosystem: pre-commit + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + pre-commit-hooks: + patterns: + - "*" + + - package-ecosystem: pip + directory: /.github/requirements + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + ci-python-tools: + patterns: + - "*" diff --git a/.github/requirements/pre-commit.txt b/.github/requirements/pre-commit.txt new file mode 100644 index 0000000..a0292e8 --- /dev/null +++ b/.github/requirements/pre-commit.txt @@ -0,0 +1,20 @@ +cfgv==3.5.0 \ + --hash=sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 +distlib==0.4.3 \ + --hash=sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b +filelock==3.32.5 \ + --hash=sha256:142cd9fa77a872c5e78c62329a0d15278fadc686eb89e760017968961a4fd6b2 +identify==2.6.19 \ + --hash=sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a +nodeenv==1.10.0 \ + --hash=sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 +platformdirs==4.11.7 \ + --hash=sha256:8a02cb259042c79d1cd0450facc2fe6dc9d303ae7901afbe33bf8ea0b188cef6 +pre-commit==4.6.2 \ + --hash=sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e +python-discovery==1.6.0 \ + --hash=sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a +PyYAML==6.0.3 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 +virtualenv==21.7.8 \ + --hash=sha256:3040eb3cbf5d32b10ffd57d167e6a162237ad82ba7d8cf1400a1efed593d85ac diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..05c51b8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + schedule: + - cron: "23 7 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: lint + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Check out repository + 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.13" + + - name: Install pre-commit + run: >- + python -m pip install --require-hashes --only-binary=:all: + --disable-pip-version-check + --requirement .github/requirements/pre-commit.txt + + - name: Run repository checks + run: pre-commit run --all-files --show-diff-on-failure + + - name: Audit GitHub Actions security + uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3 + with: + advanced-security: false + annotations: true + min-severity: medium + + configuration-security: + name: configuration security + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Scan repository configuration + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: config + scan-ref: . + format: table + exit-code: "1" + severity: CRITICAL,HIGH diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..3ba1e4f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,47 @@ +name: CodeQL + +on: + pull_request: + paths: + - ".github/workflows/**" + push: + branches: + - main + paths: + - ".github/workflows/**" + schedule: + - cron: "11 6 * * 3" + workflow_dispatch: + +permissions: read-all + +concurrency: + group: codeql-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze-actions: + name: Analyze GitHub Actions + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + actions: read + contents: read + security-events: write + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + with: + languages: actions + build-mode: none + queries: security-extended + + - name: Analyze GitHub Actions + uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + with: + category: /language:actions diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..3dec7e0 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,51 @@ +name: OpenSSF Scorecard + +on: + branch_protection_rule: + push: + branches: + - main + schedule: + - cron: "37 8 * * 6" + workflow_dispatch: + +permissions: read-all + +concurrency: + group: scorecard-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + security-events: write + id-token: write + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run Scorecard analysis + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + file_mode: git + + - name: Upload SARIF artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: openssf-scorecard-sarif + path: results.sarif + retention-days: 5 + + - name: Upload results to code scanning + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af8de40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Local environments and tool caches +.env +.venv/ +venv/ +__pycache__/ +.pytest_cache/ +.mypy_cache/ +.pre-commit-cache/ +.ruff_cache/ + +# Editors and operating systems +.idea/ +.vscode/ +.DS_Store +Thumbs.db + +# Local security and qualification evidence +*.sarif +*.spdx.json +grype*.json +scap-results*/ +security-results/ + +# Certificates and private keys generated by rehearsals +*.csr +*.key +*.p12 +*.pfx +*.pem +*.srl + +# Local container build output +build/ +dist/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0062f64 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,55 @@ +minimum_pre_commit_version: "4.0.0" +default_install_hook_types: + - pre-commit + - commit-msg + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0 + hooks: + - id: check-added-large-files + args: + - --maxkb=1024 + - id: check-case-conflict + - id: check-json + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: destroyed-symlinks + - id: detect-private-key + - id: end-of-file-fixer + - id: fix-byte-order-marker + - id: mixed-line-ending + args: + - --fix=lf + - id: trailing-whitespace + args: + - --markdown-linebreak-ext=md + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: 745eface02aef23e168a8afb6b5737818efbea95 # v0.11.0.1 + hooks: + - id: shellcheck + + - repo: https://github.com/shenxianpeng/hadolint-pre-commit + rev: 6b998985ab8b766ed8104e4fcfeb0a4765f4c364 # v2.15.1.2 + hooks: + - id: hadolint + files: (^|/)(Containerfile|Dockerfile[^/]*)$ + args: + - --failure-threshold + - warning + + - repo: https://github.com/rhysd/actionlint + rev: 914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12 + hooks: + - id: actionlint + + - repo: local + hooks: + - id: forbid-coauthor-trailers + name: forbid co-author attribution trailers + language: pygrep + entry: "(?im)^Co-Authored-By:" + stages: + - commit-msg diff --git a/CHANGELOG.md b/CHANGELOG.md index 207aaa4..4464666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,3 +17,11 @@ but container releases use the upstream-derived format documented in revisions. - Added the project overview, intended use cases, security design, rootless runtime model, and release status. +- Added pinned local pre-commit checks for repository hygiene, shell code, + container build files, GitHub Actions, private keys, and attribution trailers. +- Added least-privilege CI, CodeQL Actions, Trivy configuration, Zizmor, and + OpenSSF Scorecard workflows with immutable third-party Action references. +- Added grouped Dependabot updates for Actions, pre-commit hooks, and the + hash-locked CI Python environment. +- Documented local checks, GitHub automation, planned image assurance, and the + evidence boundary of Podman Desktop or a remote Podman machine. diff --git a/README.md b/README.md index 6cba1b3..8517296 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ root filesystem, explicit `tmpfs` mounts, dropped capabilities, and - [Changelog](CHANGELOG.md) records notable completed changes. - [Agent guidance](CLAUDE.md) defines repository implementation and security conventions. +- [Continuous integration](docs/CI.md) documents current automation, local + pre-commit checks, and the planned image assurance pipeline. Operational, TLS, configuration, architecture, threat-model, control-matrix, SCAP, vulnerability-management, support, and disconnected-network guides will @@ -110,9 +112,19 @@ tags. ## Development status The current work is governed by the dependency-ordered roadmap. Build and test -commands will be added here only when they exist and have been exercised. Until -the first signed release is published, this repository should be treated as -development material rather than a supported production image. +commands will be added here only when they exist and have been exercised. +Repository checks can be run now with: + +```console +python -m pip install --require-hashes --only-binary=:all: \ + --requirement .github/requirements/pre-commit.txt +pre-commit run --all-files --show-diff-on-failure +``` + +See the [continuous integration guide](docs/CI.md) for hook installation and +Podman evidence boundaries. Until the first signed release is published, this +repository should be treated as development material rather than a supported +production image. Security concerns should not be disclosed in a public issue. A private reporting process and supported-version policy will be published in diff --git a/docs/CI.md b/docs/CI.md new file mode 100644 index 0000000..9e0efdc --- /dev/null +++ b/docs/CI.md @@ -0,0 +1,78 @@ +# Continuous integration + +The initial automation protects repository and workflow development before the +container implementation lands. Image build, runtime, SBOM, vulnerability, and +SCAP jobs will be added with the corresponding implementations so a green job +never represents a test that could not actually run. + +## Current workflows + +| Workflow | Triggers | Current purpose | +| --- | --- | --- | +| `CI` | Pull requests, `main`, weekly, manual | Run pinned pre-commit checks, audit Actions with Zizmor, and scan repository configuration with Trivy. | +| `CodeQL` | Workflow changes, `main`, weekly, manual | Analyze GitHub Actions with the security-extended query suite. | +| `OpenSSF Scorecard` | `main`, branch-protection changes, weekly, manual | Publish repository supply-chain findings and SARIF. | + +Workflow permissions default to read-only. A job receives a write scope only +when it must publish code-scanning results. The Scorecard job also receives an +OIDC token for authenticated result publication. Third-party Actions are pinned +to full commit SHAs and are tracked by Dependabot. + +## Local repository checks + +Install the hash-locked pre-commit environment with Python 3.13 or a compatible +Python version: + +```console +python -m pip install --require-hashes --only-binary=:all: \ + --requirement .github/requirements/pre-commit.txt +pre-commit install --install-hooks +pre-commit run --all-files --show-diff-on-failure +``` + +The configured hooks check text normalization, YAML and JSON syntax, merge +markers, unsafe or broken symlinks, oversized files, private keys, shell code, +container build files, GitHub Actions, and prohibited co-author trailers. + +The `commit-msg` hook applies only after `pre-commit install` installs the +configured hook types. CI separately evaluates repository files but cannot +retroactively validate a local commit message that was never pushed. + +## Local Podman development + +Podman is the primary local container workflow. Before relying on a result, +record both client and engine details: + +```console +podman version +podman info +``` + +Podman Desktop or a remote Podman machine can run a container process as a +non-root UID while its Linux VM engine itself operates rootfully. That proves +the image's non-root process behavior but does not qualify rootless-host user +namespace behavior. Release evidence will distinguish these cases. + +After the `Containerfile` and smoke suite are implemented, the README will +publish their exercised Podman commands. Native AMD64 and ARM64 CI remains +required before an image receives supported multi-architecture status. + +## Planned image assurance + +The stable protected check names will be `lint` and `image`. The aggregate +`image` check will require both native architecture jobs after they exist. The +image pipeline will add, in dependency order: + +1. Trivy build-configuration scanning. +2. Native architecture builds and restricted-runtime smoke tests. +3. Tailored OpenSCAP evaluation against an ownership-preserving filesystem + export. +4. Trivy image vulnerability scanning. +5. SPDX inventory generation with Syft. +6. Independent fixed High/Critical vulnerability gating with Grype and a + retained full finding inventory. +7. Architecture-specific artifacts and non-pull-request SARIF publication. + +Branch protection must not require `image` until that aggregate check exists +on the default branch. Once present and proven, both `lint` and `image` become +strict, required, up-to-date checks. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 5b2d72c..0810bee 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -99,20 +99,16 @@ local evaluation but is not yet a supported release. ## Package 4: CI and supply-chain controls -- [ ] Add pre-commit checks, ShellCheck, Hadolint, Actionlint, Zizmor, YAML and - JSON validation, private-key detection, unsafe-symlink detection, and release - tag tests. - [ ] Build and smoke-test on native AMD64 and ARM64 GitHub-hosted runners. - [ ] Run Trivy configuration and image scanning with fixed High/Critical findings blocking. - [ ] Generate architecture-specific SPDX SBOMs with Syft and scan them with Grype as an independent fixed High/Critical gate; retain the complete non-blocking finding inventory for review. -- [ ] Run CodeQL for GitHub Actions and OpenSSF Scorecard with SARIF published - under minimum required permissions. -- [ ] Pin GitHub Actions to full commit SHAs and automate reviewable updates. - [ ] Retain evidence even when a blocking scan fails, without leaking secrets into artifacts. +- [ ] After the native image jobs exist on `main`, require the stable `lint` and + aggregate `image` checks in the default-branch ruleset. ## Package 5: SCAP and cyber-review package @@ -141,6 +137,10 @@ local evaluation but is not yet a supported release. ## Package 6: signed first release +- [ ] Review and approve the release version contract before selecting or + creating the first release tag. Decide the exact NGINX and named UBI version + fields, packaging revision, mutable convenience-tag policy, support lifetime, + and handling of rebuilds and repository-only changes. - [ ] Freeze the final upstream versions and digests only after image-affecting work is complete. - [ ] Review all fixed and unfixed scanner findings against authoritative