diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index baa25ece..144984cc 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 120 env: - CANDIDATE_VERSION: 0.38.5 + CANDIDATE_VERSION: 0.38.6 DAPPER_IMAGE: pasturestack-host-api-dapper:security-gate TRIVY_IMAGE: ghcr.io/aquasecurity/trivy:0.73.0@sha256:7cced7cae583819fc7806d4cbc0dbbc7cad18b99f7d3e235192e6da8c091045c COSIGN_IMAGE: ghcr.io/sigstore/cosign/cosign:v3.1.3@sha256:9e5c2f2edc34351160407ca3416c61855bdf9403c3c5936e0f0be7fc261611b8 @@ -35,8 +35,9 @@ jobs: set -euo pipefail test -z "$(git status --porcelain)" test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - test "$(git rev-list --count d56f3d0..HEAD)" = 1 - test "$(git rev-list --count --merges d56f3d0..HEAD)" = 0 + # Exactly one reviewed maintenance commit after the existing published main. + test "$(git rev-list --count b124a9cce56cc3e1082d2f72434592bccb15f6ef..HEAD)" = 1 + test "$(git rev-list --count --merges b124a9cce56cc3e1082d2f72434592bccb15f6ef..HEAD)" = 0 private_pattern=$(printf '%s|%s|%s' 'chen''21019' '10\.0\.0\.''125' 'C:\\''Users\\') if git grep -n -E "$private_pattern" -- .; then echo 'Private operator data is present in the public source tree.' >&2 @@ -67,6 +68,10 @@ jobs: "$DAPPER_IMAGE" bash -lc \ 'go version -m /go/src/github.com/PastureStack/host-api/bin/host-api' \ > evidence/host-api-go-version.txt + test "$(awk 'NR == 1 {print $2}' evidence/host-api-go-version.txt)" = go1.27.3 + grep -F 'CGO_ENABLED=0' evidence/host-api-go-version.txt + grep -F "vcs.revision=$GITHUB_SHA" evidence/host-api-go-version.txt + grep -F 'vcs.modified=false' evidence/host-api-go-version.txt awk '$1 == "#" && ($3 ~ /^v/ || $3 ~ /^[0-9]/) { print $2 "\t" $3 }' \ vendor/modules.txt > evidence/go-modules.tsv test -s evidence/go-modules.tsv @@ -140,6 +145,16 @@ jobs: --output /evidence/source-secret-scan.json /workspace rm -rf -- "$source_snapshot" + # Scan the delivered executable, not only the compressed outer package. + product_snapshot=$(mktemp -d "${RUNNER_TEMP}/host-api-product.XXXXXX") + tar --no-same-owner -xzf "dist/artifacts/host-api-${CANDIDATE_VERSION}.tar.gz" -C "$product_snapshot" + product_binary=$(find "$product_snapshot" -type f -path '*/bin/host-api') + test -n "$product_binary" + test "$(find "$product_snapshot" -type f -path '*/bin/host-api' | wc -l)" = 1 + cmp evidence/host-api-linux-amd64 "$product_binary" + (cd "$product_snapshot"; sha256sum -c */SHA256SUMSSUM; sha256sum -c */SHA256SUMS) + test "$("$product_binary" --version)" = "$CANDIDATE_VERSION" + docker run --rm \ -v "$TRIVY_CACHE:/root/.cache/trivy" \ "$TRIVY_IMAGE" image --cache-dir /root/.cache/trivy --download-db-only @@ -152,11 +167,14 @@ jobs: --output /evidence/source-vulnerability-scan.json /workspace docker run --rm --network none \ -v "$TRIVY_CACHE:/root/.cache/trivy" \ - -v "$GITHUB_WORKSPACE/dist/artifacts:/artifacts:ro" \ + -v "$product_snapshot:/artifacts:ro" \ -v "$GITHUB_WORKSPACE/evidence:/evidence" \ - "$TRIVY_IMAGE" fs --cache-dir /root/.cache/trivy --skip-db-update --offline-scan \ + "$TRIVY_IMAGE" rootfs --cache-dir /root/.cache/trivy --skip-db-update --offline-scan \ --scanners vuln,secret --severity CRITICAL,HIGH --format json \ --output /evidence/artifact-scan.json /artifacts + jq -e '[.Results[]? | select(.Type == "gobinary" and (.Target | endswith("/bin/host-api")))] | length == 1' \ + evidence/artifact-scan.json >/dev/null + rm -rf -- "$product_snapshot" docker run --rm --network none \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$TRIVY_CACHE:/root/.cache/trivy" \ diff --git a/.gitignore b/.gitignore index 25075706..cba80e31 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /bin/ /host-api /dist/ +/evidence/ /gopath/ *.sw[mnop] diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 844c62e4..0e6ef634 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -6,7 +6,7 @@ Preferred settings use `platform-*` names. Historical `cattle-*` flags, legacy D Operator lifecycle messages support `en-US` and `zh-TW`. HTTP response bodies, log streams, statistics payloads, Docker output, identifiers, and protocol errors are not translated. -The current candidate is `0.38.5`, built with Go 1.27.2 on a digest-pinned Ubuntu 26.04 image and an exact Ubuntu package snapshot. Server packaging consumes `host-api-0.38.5.tar.gz`; its filename, executable layout, architecture, legacy SHA-1 compatibility files, and SHA-256 integrity files are release contracts. Product versions must remain pure numeric `MAJOR.MINOR.PATCH` values without branding or maintenance suffixes. +The source package version is `0.38.6`, built with Go 1.27.3 on a digest-pinned Ubuntu 26.04 image, a frozen bootstrap package snapshot, and checksum-pinned official security fixes. The final builder inventory records installed versions separately from the bootstrap lock. A published package must retain its reviewed producer revision, byte-identical packaging evidence, and successful security checks of the unpacked binary. Server packaging consumes `host-api-0.38.6.tar.gz`; its filename, executable layout, architecture, legacy SHA-1 compatibility files, and SHA-256 integrity files are release contracts. Product versions must remain pure numeric `MAJOR.MINOR.PATCH` values without branding or maintenance suffixes. Before release, validate token acquisition, event forwarding, logs, stats, exec, console, Docker socket proxy, container proxy, disabled-proxy behavior, key parsing, and restart behavior. diff --git a/Dockerfile.dapper b/Dockerfile.dapper index 3df93279..5b47e6d8 100644 --- a/Dockerfile.dapper +++ b/Dockerfile.dapper @@ -6,9 +6,9 @@ COPY --from=snapshot-ca-certificates /etc/ssl/certs/ca-certificates.crt /etc/ssl COPY package/ubuntu-apt.lock /licenses/ubuntu-apt.lock ARG DAPPER_HOST_ARCH=amd64 -ARG GO_VERSION=1.27.2 -ARG GO_SHA256_amd64=ecbadb99091a3f46e31f5f934b068b1864eafa7995211b39eaddf76996045fe5 -ARG GO_SHA256_arm=e25a174051d8675f87ac720aedcd25bf89ac652c4b82f26aa47919524725ed95 +ARG GO_VERSION=1.27.3 +ARG GO_SHA256_amd64=65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40 +ARG GO_SHA256_arm=d6f4e11dd7157030d7ac11c51c8f73201a2db932d593c3106b1da11cc32bfdf8 ENV DEBIAN_FRONTEND=noninteractive ENV HOST_ARCH=${DAPPER_HOST_ARCH} \ @@ -25,6 +25,16 @@ ENV HOST_ARCH=${DAPPER_HOST_ARCH} \ SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# Official fixed packages overlay the frozen bootstrap snapshot at build time. +# Keep both original bootstrap locks and the final installed inventory. +ADD --checksum=sha256:2c418a61d7165d0cde01d9cb05ae025cc41fc75ef6d3f095996bd045ba0945dc https://security.ubuntu.com/ubuntu/pool/main/c/curl/curl_8.18.0-1ubuntu2.8_amd64.deb /tmp/curl.deb +ADD --checksum=sha256:8db913e1fa5a694ca6ba7b828bd18c5204e6853ac07fe7ed9c6e1f94e3444d8e https://security.ubuntu.com/ubuntu/pool/main/c/curl/libcurl3t64-gnutls_8.18.0-1ubuntu2.8_amd64.deb /tmp/libcurl3t64-gnutls.deb +ADD --checksum=sha256:7c0b37592f83a629cc22f0c98effa176da9edbd6d77b3f38184a9d0ddc5e0266 https://security.ubuntu.com/ubuntu/pool/main/c/curl/libcurl4t64_8.18.0-1ubuntu2.8_amd64.deb /tmp/libcurl4t64.deb +ADD --checksum=sha256:3ff73e121f6cd4f19e80ab4329dfd914108b3936c9db3708200203a9b245b34d https://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl3t64_3.5.5-1ubuntu3.7_amd64.deb /tmp/libssl3t64.deb +ADD --checksum=sha256:6965aafe69e0a49ef34d2e26f8e83d09d1c6037b7e9ee37f19b292a3c6851621 https://security.ubuntu.com/ubuntu/pool/main/o/openssl/openssl_3.5.5-1ubuntu3.7_amd64.deb /tmp/openssl.deb +ADD --checksum=sha256:51cf418571059831096abd40e0bd5debaa1ae48a6f8d87d9e4e5f7f12aa4db52 https://security.ubuntu.com/ubuntu/pool/main/o/openssl/openssl-provider-legacy_3.5.5-1ubuntu3.7_amd64.deb /tmp/openssl-provider-legacy.deb +ADD --checksum=sha256:0498bc9510149ef06abd6c09b8932e43c5bc71b68634b4b8f84488b2e0a20e82 https://security.ubuntu.com/ubuntu/pool/main/l/linux/linux-libc-dev_7.0.0-38.38_amd64.deb /tmp/linux-libc-dev.deb + RUN set -eux; \ . /licenses/ubuntu-apt.lock; \ find /etc/apt -type f \( -name '*.list' -o -name '*.sources' \) -exec sed -i \ @@ -50,6 +60,27 @@ RUN set -eux; \ runc="${UBUNTU_APT_RUNC_VERSION}" \ tar="${UBUNTU_APT_TAR_VERSION}" \ xz-utils="${UBUNTU_APT_XZ_UTILS_VERSION}"; \ + for package in curl libcurl3t64-gnutls libcurl4t64 libssl3t64 openssl openssl-provider-legacy linux-libc-dev; do \ + case "$package" in \ + curl|libcurl*) expected=8.18.0-1ubuntu2.8 ;; \ + linux-libc-dev) expected=7.0.0-38.38 ;; \ + *) expected=3.5.5-1ubuntu3.7 ;; \ + esac; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Package)" = "$package"; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Version)" = "$expected"; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Architecture)" = amd64; \ + done; \ + dpkg -i /tmp/curl.deb /tmp/libcurl3t64-gnutls.deb /tmp/libcurl4t64.deb \ + /tmp/libssl3t64.deb /tmp/openssl.deb /tmp/openssl-provider-legacy.deb /tmp/linux-libc-dev.deb; \ + for package in curl libcurl3t64-gnutls libcurl4t64 libssl3t64 openssl openssl-provider-legacy linux-libc-dev; do \ + case "$package" in \ + curl|libcurl*) expected=8.18.0-1ubuntu2.8 ;; \ + linux-libc-dev) expected=7.0.0-38.38 ;; \ + *) expected=3.5.5-1ubuntu3.7 ;; \ + esac; \ + test "$(dpkg-query -W -f='${Version}' "$package")" = "$expected"; \ + rm -f "/tmp/${package}.deb"; \ + done; \ { \ printf 'snapshot\t%s\n' "${UBUNTU_APT_SNAPSHOT}"; \ dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort; \ diff --git a/README.md b/README.md index c4019240..9782a1a7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ PastureStack is an independent community effort to preserve, audit, and moderniz ## Capabilities -The source package version is `0.38.5`. Target-bound API Key tickets require matching Engine introspection at handshake and during logs, exec, and statistics streams. Revocation or expiry closes the stream; it does not stop its container or kill an already started exec process. Legacy and full-without-expiry tokens retain their authentication contract. +The source package version is `0.38.6`, built with Go 1.27.3. Use a published release only after its source, reproducibility, vulnerability, and packaged-binary checks pass; a source version is not publication evidence. Target-bound API Key tickets require matching Engine introspection at handshake and during logs, exec, and statistics streams. Revocation or expiry closes the stream; it does not stop its container or kill an already started exec process. Legacy and full-without-expiry tokens retain their authentication contract. The authenticated backend connection declares the exact `key-audit-v1,key-delegation-v1` capabilities. A compatible Proxy requires that declaration for every verified API Key stream, including full keys, so an older embedded backend cannot bypass terminal audit. Ordinary non-Key streams retain their existing backend compatibility. diff --git a/go.mod b/go.mod index a82501b6..ff584eb5 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/PastureStack/host-api go 1.26.0 -toolchain go1.27.2 +toolchain go1.27.3 require ( github.com/PastureStack/websocket-proxy v0.23.13-0.20260809214500-903576e1d20a @@ -15,6 +15,7 @@ require ( github.com/sirupsen/logrus v1.10.2 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 + golang.org/x/sys v0.47.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) @@ -59,6 +60,5 @@ require ( go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/metric v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect - golang.org/x/sys v0.47.0 // indirect pgregory.net/rapid v1.3.0 // indirect ) diff --git a/scripts/build b/scripts/build index 5e3f83ba..bcdc3d3a 100755 --- a/scripts/build +++ b/scripts/build @@ -8,7 +8,7 @@ if [ -f ./build/bootstrap.envs ];then . ./build/bootstrap.envs fi -BUILD_VERSION=${VERSION_OVERRIDE:-0.38.5} +BUILD_VERSION=${VERSION_OVERRIDE:-0.38.6} BUILD_VERSION=${BUILD_VERSION#v} if [[ ! "$BUILD_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo 'VERSION_OVERRIDE must use MAJOR.MINOR.PATCH with no suffix' >&2 diff --git a/scripts/package b/scripts/package index 51dc28b7..9b4483a3 100755 --- a/scripts/package +++ b/scripts/package @@ -8,7 +8,7 @@ cd "$(dirname "$0")/.." ARCH=${ARCH:?"ARCH not set"} SUFFIX="" [ "${ARCH}" != "amd64" ] && SUFFIX="_${ARCH}" -RELEASE_VERSION=${VERSION_OVERRIDE:-v0.38.5} +RELEASE_VERSION=${VERSION_OVERRIDE:-v0.38.6} RELEASE_VERSION=${RELEASE_VERSION#v} CONTENT=${PASTURESTACK_PACKAGE_ID:-$(git rev-parse --short=32 HEAD)} SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)}