mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 19:51:33 +00:00
e6bb10afb6
CI run #484's Go Build & Test job failed govulncheck (M-024 hard gate). Six standard-library CVEs land in go1.25.9 + one golang.org/x/net CVE in v0.49.0; all are fixed in go1.25.10 + x/net v0.53.0 respectively. The advisories that fired were: GO-2026-4986 Quadratic string concat in net/mail.consumeComment — called via internal/api/handler/validation.go's ValidateCommonName -> mail.ParseAddress GO-2026-4977 Quadratic string concat in net/mail.consumePhrase — same call site GO-2026-4982 Bypass of meta-content URL escaping in html/template — called via internal/service/digest.go's RenderDigestHTML -> Template.Execute GO-2026-4980 Escaper bypass in html/template — same call site GO-2026-4971 Panic in net.Dial / LookupPort on Windows NUL bytes — many call sites (email notifier, SSH connector, ACME validators, validation.ValidateSafeURL, ...) GO-2026-4918 Infinite loop in net/http2 transport on bad SETTINGS_MAX_FRAME_SIZE — called via internal/connector/target/f5.go's F5Client.Authenticate -> http.Client.Do Bumps applied: * `go.mod`: `go 1.25.9` -> `go 1.25.10`; `golang.org/x/net v0.49.0` -> `v0.53.0` (kept indirect — the upgrade is force-pulled by the module-version directive; transitive deps will pick the higher). * `.github/workflows/{ci,codeql,release}.yml`: setup-go pin and the release.yml `GO_VERSION` env var bumped to 1.25.10. The security-deep-scan.yml workflow uses the major-minor `1.25` pin which auto-resolves to the latest 1.25.x and is unaffected. * `Dockerfile` + `Dockerfile.agent`: `golang:1.25-alpine@sha256:5caa...` re-pinned to `golang:1.25.10-alpine@sha256:8d22e29d960bc50cd0...` (digest looked up against `registry-1.docker.io/v2/library/golang/ manifests/1.25.10-alpine`; verified by the digest-validity ci-guard). The explicit `1.25.10-alpine` tag form replaces the moving `1.25-alpine` pin so the image-spec is reproducible end-to-end even without the digest reference. * `deploy/test/f5-mock-icontrol/Dockerfile`: `golang:1.25.9-bookworm @sha256:1a14...` re-pinned to `golang:1.25.10-bookworm@sha256: e3a54b77385b4f8a31c1...` (looked up the same way). * `deploy/test/f5-mock-icontrol/go.mod`: `go 1.25.9` -> `go 1.25.10`. * `internal/api/handler/version.go` + `api/openapi.yaml`: the `runtime.Version()`-shape comment + OpenAPI `example: go1.25.9` bumped to keep doc/example freshness. * `docs/contributor/ci-pipeline.md` + `docs/reference/connectors/ iis.md`: doc-only `Go 1.25.9` -> `Go 1.25.10` references. Verification done in-tree: * All `scripts/ci-guards/*.sh` pass locally including `digest-validity.sh` (the new digests resolve cleanly against Docker Hub). * `S-1-hardcoded-source-counts.sh` clean (the false-positive on "Bundle 1 migrations" was fixed in the prior commit). Operator step required post-push (sandbox has no Go toolchain): cd certctl && go mod tidy This regenerates go.sum's `golang.org/x/net v0.49.0` h1: lines into v0.53.0 ones. CI's `go mod tidy && git diff --exit-code go.mod go.sum` step will catch the drift if missed; in that case run the command, commit, and push the go.sum-only delta.
146 lines
5.7 KiB
Docker
146 lines
5.7 KiB
Docker
# Multi-stage build for certctl server
|
|
#
|
|
# Bundle A / Audit H-001 (CWE-829): every FROM line is pinned to an
|
|
# immutable digest in addition to the human-readable tag. The tag is
|
|
# advisory; the digest is what Docker actually pulls. A registry-side
|
|
# tag swap (the documented prior-art for tag-only pulls being unsafe)
|
|
# can no longer change the build.
|
|
#
|
|
# Bump procedure (operator):
|
|
# 1. Quarterly cadence (or sooner if a CVE lands on a base image).
|
|
# 2. For each FROM:
|
|
# docker pull <image>:<tag>
|
|
# docker manifest inspect <image>:<tag> | grep -m1 digest
|
|
# OR via Docker Hub Registry API:
|
|
# curl -sSL https://hub.docker.com/v2/repositories/library/<image>/tags/<tag> \
|
|
# | jq -r .digest
|
|
# 3. Replace the @sha256:... portion of the FROM line.
|
|
# 4. Run `docker build` locally + verify CI.
|
|
# 5. Commit with the bump procedure cited in the message body.
|
|
#
|
|
# The CI step "Forbidden bare FROM regression guard (H-001)" rejects
|
|
# any future commit that lands a FROM without an @sha256 pin.
|
|
|
|
# Stage 1: Build frontend
|
|
FROM node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS frontend
|
|
|
|
# Proxy propagation (M-4, Issue #9) — defaulted to empty so un-proxied builds
|
|
# behave identically to the pre-fix tree. When `HTTP_PROXY`/`HTTPS_PROXY`/
|
|
# `NO_PROXY` are forwarded via `docker build --build-arg` (or compose
|
|
# `build.args`), they are re-exported as ENV with both upper- and lower-case
|
|
# names because npm/apk/curl read the lowercase variants while Go, Node, and
|
|
# most HTTP libraries read the uppercase ones.
|
|
ARG HTTP_PROXY=
|
|
ARG HTTPS_PROXY=
|
|
ARG NO_PROXY=
|
|
ENV HTTP_PROXY=${HTTP_PROXY} \
|
|
HTTPS_PROXY=${HTTPS_PROXY} \
|
|
NO_PROXY=${NO_PROXY} \
|
|
http_proxy=${HTTP_PROXY} \
|
|
https_proxy=${HTTPS_PROXY} \
|
|
no_proxy=${NO_PROXY}
|
|
|
|
WORKDIR /app/web
|
|
|
|
COPY web/ .
|
|
# Bundle A / Audit M-014: explicit retry loop for `npm ci`. Pre-bundle
|
|
# this was `npm ci || npm ci && tsc && build` — the bash precedence is
|
|
# `A || (B && C && D)` so the second `npm ci` only ran on the failure
|
|
# path of the first, but the `tsc && build` chain only ran on the
|
|
# success path of the second. Net effect: a transient registry blip
|
|
# turned the build into a silent skip of the production step.
|
|
#
|
|
# New shape: a deterministic 3-attempt retry with 5-second backoff and
|
|
# an explicit `[ -d node_modules ]` post-check so a silent failure is
|
|
# impossible.
|
|
RUN for i in 1 2 3; do \
|
|
npm ci --include=dev && break; \
|
|
echo "npm ci attempt $i failed; sleeping 5s before retry"; \
|
|
sleep 5; \
|
|
done && \
|
|
[ -d node_modules ] || (echo "ERROR: npm ci failed after 3 attempts; node_modules missing" && exit 1) && \
|
|
node_modules/.bin/tsc --version && \
|
|
npm run build
|
|
|
|
# Stage 2: Build Go binary
|
|
FROM golang:1.25.10-alpine@sha256:8d22e29d960bc50cd025d93d5b7c7d220b1ee9aa7a239b3c8f55a57e987e8d45 AS builder
|
|
|
|
# Proxy propagation (M-4, Issue #9) — see Stage 1 rationale.
|
|
ARG HTTP_PROXY=
|
|
ARG HTTPS_PROXY=
|
|
ARG NO_PROXY=
|
|
ENV HTTP_PROXY=${HTTP_PROXY} \
|
|
HTTPS_PROXY=${HTTPS_PROXY} \
|
|
NO_PROXY=${NO_PROXY} \
|
|
http_proxy=${HTTP_PROXY} \
|
|
https_proxy=${HTTPS_PROXY} \
|
|
no_proxy=${NO_PROXY}
|
|
|
|
RUN apk add --no-cache git ca-certificates tzdata
|
|
|
|
WORKDIR /app
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
|
|
# Build server binary (use TARGETARCH for multi-platform support)
|
|
ARG TARGETARCH=amd64
|
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build \
|
|
-ldflags="-w -s" \
|
|
-o bin/server \
|
|
./cmd/server
|
|
|
|
# Stage 3: Runtime
|
|
FROM alpine:3.19@sha256:6baf43584bcb78f2e5847d1de515f23499913ac9f12bdf834811a3145eb11ca1
|
|
|
|
RUN apk add --no-cache ca-certificates tzdata curl
|
|
|
|
RUN addgroup -g 1000 certctl && \
|
|
adduser -D -u 1000 -G certctl certctl
|
|
|
|
WORKDIR /app
|
|
|
|
COPY --from=builder /app/bin/server .
|
|
COPY --chown=certctl:certctl migrations/ ./migrations/
|
|
COPY --from=frontend --chown=certctl:certctl /app/web/dist/ ./web/dist/
|
|
|
|
RUN chown -R certctl:certctl /app
|
|
|
|
USER certctl
|
|
|
|
EXPOSE 8443
|
|
|
|
# Image-level HEALTHCHECK for bare `docker run` / Docker Swarm / Nomad / ECS.
|
|
#
|
|
# U-2 (P1, cat-u-healthcheck_protocol_mismatch): pre-U-2 this probe used
|
|
# `curl -f http://localhost:8443/health`, which always failed against the
|
|
# HTTPS-only listener (HTTPS-Everywhere milestone, v2.2 / tag v2.0.47 —
|
|
# `cmd/server/main.go::ListenAndServeTLS`, no plaintext fallback, TLS 1.3
|
|
# pinned). Operators outside docker-compose / Helm saw permanent
|
|
# `unhealthy` status and a restart-loop the first time they pulled the
|
|
# image. The compose stack overrides this HEALTHCHECK with `--cacert` to
|
|
# the bootstrap CA bundle (deploy/docker-compose.yml:126); the Helm chart
|
|
# uses explicit `httpGet` probes with `scheme: HTTPS` and ignores Docker's
|
|
# HEALTHCHECK; every example compose file in `examples/*/docker-compose.yml`
|
|
# overrides with `curl -sfk https://localhost:8443/health`. This image-
|
|
# level probe is for the bare-`docker run` consumer ONLY.
|
|
#
|
|
# `-k` (insecure) is acceptable here because the probe is localhost-to-
|
|
# localhost: the same process serving the cert is being probed; the probe
|
|
# never traverses a network. Pinning a `--cacert` is not viable for the
|
|
# published image because the bootstrap cert is per-deploy (generated into
|
|
# the `certs` named volume on first up; operator-supplied via Helm's
|
|
# `existingSecret` or cert-manager). Compose / Helm / examples already
|
|
# perform full cert-chain validation and are unaffected.
|
|
#
|
|
# CI grep guardrail at .github/workflows/ci.yml ("Forbidden plaintext
|
|
# HEALTHCHECK regression guard (U-2)") blocks reintroduction of the
|
|
# `http://` shape. Image-level integration test in
|
|
# deploy/test/healthcheck_test.go pins the contract end-to-end.
|
|
HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=5 \
|
|
CMD curl -fsk https://localhost:8443/health || exit 1
|
|
|
|
ENTRYPOINT ["/app/server"]
|