ci: pin actions to commit SHAs, guard Docker jobs from forks, scan the image, gate publish on security (SEC-06, SEC-07, SEC-14, SEC-15, SEC-16)
This commit is contained in:
1 parent
6f7a3c0e5b
commit
766968fa42
9 files changed
+679
-60
No files matched your search
+79
-29
@@ -4,13 +4,21 @@ name: ci
|
||||
#
|
||||
# gitleaks secret scan over the full history
|
||||
# lint ruff check, ruff format --check, mypy
|
||||
# test-unit pytest (coverage gate from pyproject.toml)
|
||||
# test-unit pytest (coverage gate from pyproject.toml: 90 percent)
|
||||
# test-integration pytest against a throwaway Gitea in Docker
|
||||
# test-mcp MCP smoke tests, including against the freshly built image
|
||||
# build-and-push build the image; push it on push events (needs lint + test-unit)
|
||||
# build-and-push build and scan the image; push it on push events (needs lint + test-unit)
|
||||
# docs-check docs drift check
|
||||
# security security test suite, pip-audit, ruff bandit rules
|
||||
#
|
||||
# Jobs that use the shared Docker daemon or secrets (test-integration, test-mcp,
|
||||
# build-and-push) do not run for pull requests from forks: a fork controls the
|
||||
# workflow and test code, and access to the daemon is root-equivalent on the
|
||||
# host. Also enable "require approval for workflows from forks" in Gitea.
|
||||
#
|
||||
# Every third-party action is pinned to a full commit SHA (the version is in the
|
||||
# trailing comment); bump deliberately.
|
||||
#
|
||||
# Docker hygiene on the shared runner: every resource this workflow creates has
|
||||
# a per-run name (aegis-ci-<run id>-...) and is removed by an `if: always()`
|
||||
# step that deletes exactly those names. Nothing here ever prunes.
|
||||
@@ -37,9 +45,10 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install gitleaks
|
||||
shell: bash
|
||||
@@ -60,13 +69,15 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
with:
|
||||
version: "0.12.5"
|
||||
enable-cache: false
|
||||
@@ -87,13 +98,15 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
with:
|
||||
version: "0.12.5"
|
||||
enable-cache: false
|
||||
@@ -105,7 +118,7 @@ jobs:
|
||||
# Best effort: some act_runner versions do not support the v4 artifact
|
||||
# backend, and the coverage gate itself already ran above.
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: coverage-${{ github.run_id }}
|
||||
path: coverage.xml
|
||||
@@ -117,17 +130,20 @@ jobs:
|
||||
# cleanup step is the safety net for cancelled runs.
|
||||
# ---------------------------------------------------------------------------
|
||||
test-integration:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
with:
|
||||
version: "0.12.5"
|
||||
enable-cache: false
|
||||
@@ -151,19 +167,22 @@ jobs:
|
||||
# runners.
|
||||
# ---------------------------------------------------------------------------
|
||||
test-mcp:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
MCP_IMAGE: aegis-ci-${{ github.run_id }}-mcp:test
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
with:
|
||||
version: "0.12.5"
|
||||
enable-cache: false
|
||||
@@ -196,6 +215,7 @@ jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
build-and-push:
|
||||
needs: [lint, test-unit]
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
@@ -203,7 +223,9 @@ jobs:
|
||||
digest: ${{ steps.digest.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Compute image name and tags
|
||||
id: meta
|
||||
@@ -237,15 +259,36 @@ jobs:
|
||||
shell: bash
|
||||
run: docker run --rm "${{ steps.meta.outputs.sha_tag }}" version
|
||||
|
||||
# Vulnerability scan of the image that is about to be pushed: pinned Trivy
|
||||
# (sha256 verified), fails on fixable HIGH and CRITICAL findings. Waivers live
|
||||
# in security/trivyignore.txt and docs/security.md.
|
||||
- name: Install Trivy
|
||||
shell: bash
|
||||
run: |
|
||||
bin_dir="${RUNNER_TEMP:-/tmp}/trivy-bin"
|
||||
bash scripts/ci/install_trivy.sh "${bin_dir}"
|
||||
echo "${bin_dir}" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Scan image
|
||||
shell: bash
|
||||
run: bash scripts/ci/scan_image.sh "${{ steps.meta.outputs.sha_tag }}"
|
||||
|
||||
# No third-party action sees the registry token: the CLI reads it from stdin. The
|
||||
# credentials go to a per-job config directory (DOCKER_CONFIG), never into the shared
|
||||
# credential store of the runner's Docker client.
|
||||
- name: Log in to the registry
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.hiddenden.cafe
|
||||
username: ${{ github.actor }}
|
||||
shell: bash
|
||||
env:
|
||||
# PAT with write:package scope. The built-in token lacks package-write
|
||||
# on this instance.
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
REGISTRY_USER: ${{ github.actor }}
|
||||
run: |
|
||||
config_dir="$(mktemp -d)"
|
||||
echo "DOCKER_CONFIG=${config_dir}" >> "$GITHUB_ENV"
|
||||
printf '%s' "${REGISTRY_TOKEN}" | DOCKER_CONFIG="${config_dir}" \
|
||||
docker login git.hiddenden.cafe --username "${REGISTRY_USER}" --password-stdin
|
||||
|
||||
- name: Tag and push
|
||||
if: github.event_name == 'push'
|
||||
@@ -284,7 +327,10 @@ jobs:
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
docker logout git.hiddenden.cafe || true
|
||||
# Only the per-job credential directory created above is removed.
|
||||
if [ -n "${DOCKER_CONFIG:-}" ]; then
|
||||
rm -rf "${DOCKER_CONFIG}"
|
||||
fi
|
||||
bash scripts/ci/cleanup_job_resources.sh \
|
||||
"${{ steps.meta.outputs.sha_tag }}" ${{ steps.meta.outputs.extra_tags }}
|
||||
|
||||
@@ -296,13 +342,15 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
with:
|
||||
version: "0.12.5"
|
||||
enable-cache: false
|
||||
@@ -321,13 +369,15 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
with:
|
||||
version: "0.12.5"
|
||||
enable-cache: false
|
||||
|
||||
Reference in new issue
Block a user