# Pinned lint toolchain image (Phase 313). # # Bakes the project's pinned Python environment plus every third-party Python # linter used by `make lint`, so CI and local runs share ONE reproducible # toolchain and never depend on an ad-hoc host `pip install` (which is what kept # flaking the Full Lint gate). # # Tools that already ship their own official images — hadolint, shellcheck, # trivy, gitleaks, gosec, promtool, amtool, scorecard, lychee — are run from # those images directly by the Makefile and are intentionally NOT installed here. # The Go toolchain (go fmt/vet) is provided by the CI runner / host, not here. # checkov:skip=CKV_DOCKER_3: This is an internal lint/test-runner image that writes # cache files to the bind-mounted /src volume (owned by the host user). Running as a # non-root user breaks write access; no network exposure — not a runtime service image. FROM python:3.15.2-slim@sha256:8c13a3f37a6a5b71498228e902c4b9f51c1f7900d49fabf71f9a650c6120e0d0 # git + certs: needed by pip-audit and a few linters. bash ships in the base. # build-essential: some pinned deps (e.g. pytricia) build C extensions from sdist. # xmlsec1: required by python3-saml (management dep) for SAML signature validation. RUN apt-get update \ && apt-get install -y --no-install-recommends git ca-certificates build-essential xmlsec1 curl \ && rm -rf /var/lib/apt/lists/* # helm CLI (pinned, checksum-verified) — required for # tests/unit/test_pentest_helm_probes_and_networkpolicy_regression.py, which # runs `helm template` in-process to assert Helm-chart security invariants # (DaemonSet liveness/readiness probes, NetworkPolicy defaults). Without it on # PATH here, all 7 of those tests silently `pytest.skip()` in every `make # test` run instead of actually checking anything (phase-514). This is a # separate, deliberately minimal static-binary install from the # `alpine/helm:3.17.3` image `lint-helm` uses via Docker — that target lints # the chart from outside; this test renders it in-process and needs the # binary directly on PATH. ARG HELM_VERSION=3.17.3 ARG HELM_SHA256=ee88b3c851ae6466a3de507f7be73fe94d54cbf2987cbaa3d1a3832ea331f2cd RUN curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors -o /tmp/helm.tar.gz "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" \ && echo "${HELM_SHA256} /tmp/helm.tar.gz" | sha256sum -c - \ && tar -xzf /tmp/helm.tar.gz -C /tmp linux-amd64/helm \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm \ && chmod +x /usr/local/bin/helm \ && rm -rf /tmp/helm.tar.gz /tmp/linux-amd64 # promtool CLI (pinned, checksum-verified) — required for # tests/integration/test_phase_86h_alertmanager_runbook_urls.py, which checks # the on-disk Alertmanager/Prometheus rule files directly with `promtool # check rules` (not via Docker). Pinned to the SAME version (v2.48.0) as # `lint-prom`'s `prom/prometheus:v2.48.0` image so the two checks of the same # rule files can never silently disagree (phase-514 also fixed one drifted # tool version already — see the golangci-lint pin above this file's sibling # fix in Makefile). ARG PROMETHEUS_VERSION=2.48.0 ARG PROMETHEUS_SHA256=5871ca9e01ae35bb7ab7a129a845a7a80f0e1453f00f776ac564dd41ff4d754e RUN curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors -o /tmp/prom.tar.gz "https://github.com/prometheus/prometheus/releases/download/v${PROMETHEUS_VERSION}/prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz" \ && echo "${PROMETHEUS_SHA256} /tmp/prom.tar.gz" | sha256sum -c - \ && tar -xzf /tmp/prom.tar.gz -C /tmp "prometheus-${PROMETHEUS_VERSION}.linux-amd64/promtool" \ && mv "/tmp/prometheus-${PROMETHEUS_VERSION}.linux-amd64/promtool" /usr/local/bin/promtool \ && chmod +x /usr/local/bin/promtool \ && rm -rf /tmp/prom.tar.gz "/tmp/prometheus-${PROMETHEUS_VERSION}.linux-amd64" # docker CLI client only (no daemon/containerd/runc — this container talks to # the HOST's Docker daemon via the bind-mounted socket, see Makefile # TOOLS_RUN). Required for tests/integration/test_container_config.py, # test_dockerfile_coverage.py, and test_go_proxy_image.py, which all # `shutil.which("docker")`-gate on the CLI being present and otherwise # silently skip. No official per-file checksum is published for the static # binary bundle (unlike Helm/Prometheus above), so this hash was computed # directly from the file fetched over TLS from download.docker.com and is # pinned here to catch any future substitution. ARG DOCKER_VERSION=29.6.1 ARG DOCKER_SHA256=b0df4a43a98d7ecb708acbdb5a34a3416e13b6e39bcbbdf296f51f0f3442b29f RUN curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors -o /tmp/docker.tgz "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz" \ && echo "${DOCKER_SHA256} /tmp/docker.tgz" | sha256sum -c - \ && tar -xzf /tmp/docker.tgz -C /tmp docker/docker \ && mv /tmp/docker/docker /usr/local/bin/docker \ && chmod +x /usr/local/bin/docker \ && rm -rf /tmp/docker.tgz /tmp/docker # docker compose CLI plugin (pinned, checksum-verified) — `docker compose config` # in test_container_config.py/test_dockerfile_coverage.py needs the `compose` # subcommand, which the docker static client tarball above does NOT include # (it's a separate CLI plugin, installed to the plugins dir, not the base binary). ARG COMPOSE_VERSION=5.2.0 ARG COMPOSE_SHA256=018f9612ecabc5f2d7aaa53d6f5f44453a87611e2d72c8ef84d7b1eca070e719 RUN mkdir -p /usr/local/lib/docker/cli-plugins \ && curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors -o /usr/local/lib/docker/cli-plugins/docker-compose "https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-linux-x86_64" \ && echo "${COMPOSE_SHA256} /usr/local/lib/docker/cli-plugins/docker-compose" | sha256sum -c - \ && chmod +x /usr/local/lib/docker/cli-plugins/docker-compose # docker buildx CLI plugin (pinned, checksum-verified against docker/buildx's # published checksums.txt) — this repo's Dockerfiles use BuildKit-only # `RUN --mount=type=cache,...` syntax, which the legacy builder rejects. # Current Docker CLI versions require the buildx plugin to actually be # present to use BuildKit — DOCKER_BUILDKIT=1 alone is no longer sufficient # without it (confirmed: fails with "BuildKit is enabled but the buildx # component is missing or broken" otherwise). ARG BUILDX_VERSION=0.35.0 ARG BUILDX_SHA256=d41ece72044243b4f58b343441ae37446d9c29a7d6b5e11c61847bbcf8f7dfda RUN curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors -o /usr/local/lib/docker/cli-plugins/docker-buildx "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-amd64" \ && echo "${BUILDX_SHA256} /usr/local/lib/docker/cli-plugins/docker-buildx" | sha256sum -c - \ && chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx # Set as a default here so every `docker build` invoked from this image # (including from Python subprocess calls in tests, which don't set it # themselves) uses BuildKit without every call site needing to remember it — # the Makefile already sets this ad-hoc in several targets; this makes it the # image's default instead. ENV DOCKER_BUILDKIT=1 WORKDIR /src # Project deps first (own layer, cached unless requirements change) so mypy can # resolve imports and pytest/conftest can import redis, fastapi, etc. # Management deps are included so pylint can resolve all imports. COPY requirements.txt requirements-test.txt requirements-analytics.txt requirements-lint-management.txt ./ COPY management/requirements.txt ./management/requirements.txt RUN pip install --no-cache-dir \ -r requirements.txt \ -r requirements-test.txt \ -r requirements-analytics.txt \ -r requirements-lint-management.txt \ -r management/requirements.txt # Third-party Python linters (pinned major versions) that are compatible with # the project's Python 3.15. semgrep and ansible-lint are NOT installed here — # they don't yet run on 3.15, so the Makefile runs semgrep from its official # image (semgrep/semgrep) and treats ansible-lint as advisory. RUN pip install --no-cache-dir \ ruff==0.4.* \ mypy==1.* \ pip-audit==2.* \ pymarkdownlnt==0.9.* \ codespell==2.* \ yamllint==1.35.* # yamllint (phase-800): `lint-yaml` used to call a bare host `yamllint` with no # `command -v` guard, so it hard-failed on any machine that happened not to have # it and silently used whatever version the host had otherwise. It is the last # Python linter that was not pinned here. # NOTE: bandit is intentionally NOT installed here. It runs on Python 3.11 from # Dockerfile.bandit — its B502/B604/B607 plugins use ast.Num (removed in 3.12), # so on this 3.15 image bandit silently skips whole files. See Dockerfile.bandit. HEALTHCHECK NONE