diff --git a/.github/workflows/codeql-verification.yml b/.github/workflows/codeql-verification.yml index 055ed64..eed6ebe 100644 --- a/.github/workflows/codeql-verification.yml +++ b/.github/workflows/codeql-verification.yml @@ -53,7 +53,7 @@ jobs: cd "$GITHUB_WORKSPACE" go test -run '^$' ./... go test -run '^$' -tags=integration ./store - VERSION_OVERRIDE=0.14.35 ./scripts/build + VERSION_OVERRIDE=0.14.36 ./scripts/build - name: Analyze without publishing temporary alerts uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afdd297..70fa962 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,6 @@ name: Release IPsec overlay -# A release is dispatched from main only after an annotated v0.14.35 tag and +# A release is dispatched from main only after an annotated v0.14.36 tag and # successful, same-commit Security release gate and CodeQL verification runs. # GHCR, registry attestations, and GitHub Releases are not an atomic transaction. # All source/product/runtime gates precede the image push. A later failure may @@ -9,11 +9,11 @@ on: workflow_dispatch: inputs: release_tag: - description: Existing annotated, numeric v0.14.35 tag at main HEAD + description: Existing annotated, numeric v0.14.36 tag at main HEAD required: true type: choice options: - - v0.14.35 + - v0.14.36 security_run_id: description: Successful same-SHA Security release gate run ID required: true @@ -35,7 +35,7 @@ permissions: attestations: write concurrency: - group: ipsec-vxlan-overlay-release-v0.14.35 + group: ipsec-vxlan-overlay-release-v0.14.36 cancel-in-progress: false jobs: @@ -48,8 +48,8 @@ jobs: CODEQL_RUN_ID: ${{ inputs.codeql_run_id }} RESUME_DIGEST: ${{ inputs.resume_digest }} GH_TOKEN: ${{ github.token }} - SERVICE_IMAGE: ghcr.io/pasturestack/ipsec-vxlan-overlay-network:v0.14.35 - LOCAL_IMAGE: local/pasturestack/ipsec-vxlan-overlay-network:0.14.35 + SERVICE_IMAGE: ghcr.io/pasturestack/ipsec-vxlan-overlay-network:v0.14.36 + LOCAL_IMAGE: local/pasturestack/ipsec-vxlan-overlay-network:0.14.36 TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 GOFLAGS: -mod=vendor GOWORK: off @@ -67,7 +67,7 @@ jobs: run: | set -euo pipefail test "$GITHUB_REF" = refs/heads/main - test "$RELEASE_TAG" = v0.14.35 + test "$RELEASE_TAG" = v0.14.36 [[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] [[ "$SECURITY_RUN_ID" =~ ^[0-9]+$ ]] [[ "$CODEQL_RUN_ID" =~ ^[0-9]+$ ]] @@ -152,15 +152,15 @@ jobs: shell: bash run: | set -euo pipefail - VERSION_OVERRIDE=0.14.35 ./scripts/build + VERSION_OVERRIDE=0.14.36 ./scripts/build sha256sum --check evidence/product.sha256 if [ -z "$RESUME_DIGEST" ]; then - VERSION_OVERRIDE=0.14.35 TAG=0.14.35 REPO=local/pasturestack \ + VERSION_OVERRIDE=0.14.36 TAG=0.14.36 REPO=local/pasturestack \ IMAGE_REVISION="$SOURCE_SHA" ./scripts/package test "$(wc -l < dist/images)" -eq 1 grep -Fx "$LOCAL_IMAGE" dist/images test "$(docker image inspect "$LOCAL_IMAGE" \ - --format '{{index .Config.Labels "org.opencontainers.image.version"}}')" = 0.14.35 + --format '{{index .Config.Labels "org.opencontainers.image.version"}}')" = 0.14.36 test "$(docker image inspect "$LOCAL_IMAGE" \ --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$SOURCE_SHA" docker tag "$LOCAL_IMAGE" "$SERVICE_IMAGE" @@ -178,7 +178,7 @@ jobs: docker pull "$SERVICE_IMAGE@$RESUME_DIGEST" docker tag "$SERVICE_IMAGE@$RESUME_DIGEST" "$SERVICE_IMAGE" test "$(docker image inspect "$SERVICE_IMAGE" \ - --format '{{index .Config.Labels "org.opencontainers.image.version"}}')" = 0.14.35 + --format '{{index .Config.Labels "org.opencontainers.image.version"}}')" = 0.14.36 test "$(docker image inspect "$SERVICE_IMAGE" \ --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$SOURCE_SHA" verification_container="$(docker create "$SERVICE_IMAGE")" @@ -315,7 +315,7 @@ jobs: run: | set -euo pipefail release_dir="dist/release" - product_dir="$release_dir/ipsec-vxlan-overlay-network-0.14.35-linux-amd64" + product_dir="$release_dir/ipsec-vxlan-overlay-network-0.14.36-linux-amd64" mkdir -p "$product_dir" cp bin/ipsec-vxlan-overlay-network bin/ipsec-vxlan-overlay-topology bin/pasture-cni-resolver \ LICENSE ORIGIN.md SECURITY.md THIRD_PARTY_NOTICES.md "$product_dir/" @@ -327,11 +327,11 @@ jobs: find "$product_dir" -exec touch -h -d "@$source_epoch" {} + tar --sort=name --mtime="@$source_epoch" --owner=0 --group=0 \ --numeric-owner -C "$release_dir" \ - -cJf "$release_dir/ipsec-vxlan-overlay-network-0.14.35-linux-amd64.tar.xz" \ - ipsec-vxlan-overlay-network-0.14.35-linux-amd64 + -cJf "$release_dir/ipsec-vxlan-overlay-network-0.14.36-linux-amd64.tar.xz" \ + ipsec-vxlan-overlay-network-0.14.36-linux-amd64 ( cd "$release_dir" - sha256sum ipsec-vxlan-overlay-network-0.14.35-linux-amd64.tar.xz \ + sha256sum ipsec-vxlan-overlay-network-0.14.36-linux-amd64.tar.xz \ source-sbom.cdx.json image-sbom.cdx.json product.sha256 \ runtime-security.json gate-security-summary.txt image-digest.txt \ release-identity.txt > SHA256SUMS @@ -355,9 +355,9 @@ jobs: run: | set -euo pipefail gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --verify-tag \ - --title 'IPsec Overlay v0.14.35' \ + --title 'IPsec Overlay v0.14.36' \ --notes "Source ${SOURCE_SHA}; Security gate run ${SECURITY_RUN_ID}; CodeQL run ${CODEQL_RUN_ID}. GHCR image ${SERVICE_IMAGE}@$(cat dist/release/image-digest.txt | sed 's/^.*@//')." \ - dist/release/ipsec-vxlan-overlay-network-0.14.35-linux-amd64.tar.xz \ + dist/release/ipsec-vxlan-overlay-network-0.14.36-linux-amd64.tar.xz \ dist/release/source-sbom.cdx.json \ dist/release/image-sbom.cdx.json \ dist/release/product.sha256 \ diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index ac850d8..bdcb0f8 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -18,12 +18,12 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 120 env: - CANDIDATE_VERSION: 0.14.35 - VERSION_OVERRIDE: 0.14.35 - TAG: 0.14.35 + CANDIDATE_VERSION: 0.14.36 + VERSION_OVERRIDE: 0.14.36 + TAG: 0.14.36 IMAGE_REVISION: ${{ github.sha }} REPO: local/pasturestack - IMAGE: local/pasturestack/ipsec-vxlan-overlay-network:0.14.35 + IMAGE: local/pasturestack/ipsec-vxlan-overlay-network:0.14.36 GO_VERSION: 1.27.0 GO_LINUX_AMD64_SHA256: 675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685 TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 @@ -280,7 +280,7 @@ jobs: impact_statement:$impact }] | { "@context":"https://openvex.dev/ns/v0.2.0", - "@id":"https://github.com/PastureStack/ipsec-vxlan-overlay-network/security/openvex/dapper/0.14.35", + "@id":"https://github.com/PastureStack/ipsec-vxlan-overlay-network/security/openvex/dapper/0.14.36", author:"PastureStack Security", timestamp:$timestamp, version:1, diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 4fe88b8..b236936 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -12,6 +12,7 @@ PastureStack names are the public interface for new deployments. A limited set o - XFRM and host-route variables: `PASTURESTACK_NETWORK_XFRM_*`, `PASTURESTACK_NETWORK_RUN_IN_HOST_NETNS`, and `PASTURESTACK_NETWORK_SYNC_HOST_ROUTES` - Host firewall selection: `PASTURESTACK_FIREWALL_BACKEND=auto|nftables|iptables-nft|iptables-legacy` for the IPsec host-XFRM router. From `v0.14.28`, it reads Docker's actual `/info.FirewallBackend.Driver` from the mounted Unix socket and then validates the uniquely active, matching host firewall hooks. An old Docker release with no native nftables support may omit this API field; a Docker 29+ release omitting it is ambiguous and fails closed. A mounted Unix socket is a privileged API capability even if its bind mount says `:ro`. - CNI log: `/var/log/pasturestack-cni.log` +- Flat CNI: the bundled `rancher-cni-bridge` v0.7.2 must honor `skipBridgeConfigureIP: true`. The Layer 2 Catalog template uses an operator-owned bridge; the CNI driver must not add a gateway address or otherwise reconfigure that bridge. The isolated image gate checks this contract with a real network namespace. - Platform CA: `/var/lib/pasturestack/etc/ssl/ca.crt` ## Required compatibility identifiers diff --git a/README.md b/README.md index 423b0b4..a5e9dd9 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ cross-host TCP, local Metadata/DNS, and HTTPS egress passed in both workload namespaces. Startup is asynchronous, so a service-level healthy status alone must not be used as proof that a restarted workload has acquired its IP. -The `v0.14.35` candidate addresses a separate rolling-upgrade handoff: a +`v0.14.35` addressed a separate rolling-upgrade handoff: a replacement connectivity-check can briefly share the prior generation's network namespace while the old sidecar still owns TCP 80. It waits at most 90 seconds for that listener to leave, retrying only `EADDRINUSE`; an unrelated @@ -98,7 +98,7 @@ bind error or exhausted deadline still fails clearly. This stays inside the connectivity-check module and does not alter host firewall rules, Docker backend selection, or the router's port 8111 ownership. Source tests hold and release the port and verify the bounded failure path. Image publication, -Catalog pinning, and live upgrade acceptance remain separate gates. +Catalog pinning, and live upgrade acceptance are separate gates for each release. The release gate rejects Critical/High findings and secrets in the source, shipped binaries, and runtime image. It scans the disposable Dapper builder @@ -124,8 +124,8 @@ The build is containerized and requires Docker on a Linux AMD64 host: ```sh make test make validate -VERSION_OVERRIDE=0.14.35 make build -TAG=0.14.35 make package +VERSION_OVERRIDE=0.14.36 make build +TAG=0.14.36 make package ``` The package build downloads dependencies anonymously, verifies every standalone binary with SHA-256, pins the Ubuntu base image by digest, resolves every directly installed package from Canonical snapshot `20260808T000000Z` with the exact versions in `ubuntu-apt.lock`, and includes the corresponding strongSwan source archives in the image. Go dependencies are declared in `go.mod`, checksum-bound by `go.sum`, and committed in the standard module-aware `vendor` tree for offline builds. @@ -137,6 +137,11 @@ bridge connectivity, and DEL. A local reproduction must set `IMAGE` to the exact image under review. This does not replace a live Catalog, Metadata API, host-port, cross-host, or host-reboot acceptance test. +The flat-network gate deliberately uses a preconfigured bridge address that +differs from the subnet's network address and checks that CNI does not add a +second address. The packaged bridge compatibility binary is v0.7.2, the first +release in this dependency line that recognizes `skipBridgeConfigureIP`. + The health reconciler canonicalizes strongSwan VICI CHILD_SA runtime names before comparing them with configured peer names. This prevents a VICI unique-ID suffix from being misclassified as a missing SA during a rolling replacement. ## Host firewall backends diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index b366d89..412eb36 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -6,13 +6,13 @@ PastureStack does not relicense third-party work. Copyright, license, and attrib | --- | --- | --- | --- | | strongSwan | Ubuntu `6.0.4-1ubuntu3.1` | [Ubuntu source package](https://packages.ubuntu.com/source/resolute-updates/strongswan) | GPL-2.0-or-later with OpenSSL exception, plus file-specific licenses listed by Ubuntu | | CNI reference plugins | 0.3.0 | [containernetworking/plugins](https://github.com/containernetworking/plugins) | Apache-2.0 | -| CNI bridge compatibility binary | 0.3.1 | [rancher/rancher-cni-bridge](https://github.com/rancher/rancher-cni-bridge) | Apache-2.0 | +| CNI bridge compatibility binary | 0.7.2 | [rancher/rancher-cni-bridge](https://github.com/rancher/rancher-cni-bridge) | Apache-2.0 | | Weave router helper | r-v0.0.4 | [rancher-archives/weave](https://github.com/rancher-archives/weave) | Apache-2.0 | -| Metadata CNI IPAM | 0.2.6 | [PastureStack/metadata-cni-ipam](https://github.com/PastureStack/metadata-cni-ipam) | Apache-2.0 | -| Host-local CNI IPAM | 0.1.3 | [PastureStack/host-local-cni-ipam](https://github.com/PastureStack/host-local-cni-ipam) | Apache-2.0 | -| Flat CNI IPAM | 0.1.3 | [PastureStack/flat-cni-ipam](https://github.com/PastureStack/flat-cni-ipam) | Apache-2.0 | -| Per-host subnet | 0.2.7 | [PastureStack/per-host-subnet](https://github.com/PastureStack/per-host-subnet) | Apache-2.0 | -| Mount propagation | 1.0.10 | [PastureStack/mount-propagation](https://github.com/PastureStack/mount-propagation) | Apache-2.0 | +| Metadata CNI IPAM | 0.2.7 | [PastureStack/metadata-cni-ipam](https://github.com/PastureStack/metadata-cni-ipam) | Apache-2.0 | +| Host-local CNI IPAM | 0.1.4 | [PastureStack/host-local-cni-ipam](https://github.com/PastureStack/host-local-cni-ipam) | Apache-2.0 | +| Flat CNI IPAM | 0.1.4 | [PastureStack/flat-cni-ipam](https://github.com/PastureStack/flat-cni-ipam) | Apache-2.0 | +| Per-host subnet | 0.2.8 | [PastureStack/per-host-subnet](https://github.com/PastureStack/per-host-subnet) | Apache-2.0 | +| Mount propagation | 1.0.11 | [PastureStack/mount-propagation](https://github.com/PastureStack/mount-propagation) | Apache-2.0 | The reachable Go dependency graph is declared in [`go.mod`](go.mod), checksum-bound by [`go.sum`](go.sum), and materialized in the standard module-aware `vendor` tree. Unreachable historical test-server dependencies are not shipped. diff --git a/package/Dockerfile b/package/Dockerfile index 86019ea..a46e5be 100644 --- a/package/Dockerfile +++ b/package/Dockerfile @@ -10,8 +10,8 @@ ARG STRONGSWAN_DEBIAN_SHA256=2813fea68dc93da2c17cc0c7c8a30e39b61dba333c8bdf7a7e0 ARG STRONGSWAN_DSC_SHA256=64c7e3ad1d44ff5b5e287cf02a65e4b51b351a854a2773d90fbb4a2ff8f1f39b ARG CNI_VERSION=v0.3.0 ARG CNI_SHA256=b1ae09833a238c51161918a8849031efdb46cf0068ea5b752e362d9836e2af7d -ARG CNI_BRIDGE_VERSION=v0.3.1 -ARG CNI_BRIDGE_SHA256=478f9e04772da427455e0bd90c708cc9b96f4cab89acfc0fd67c13cd779e6c34 +ARG CNI_BRIDGE_VERSION=v0.7.2 +ARG CNI_BRIDGE_SHA256=e9050b13aaa769a7a22b287c76d5dbac80cbee390f4c0b0b6db23a73f90cee59 ARG WEAVE_ROUTER_VERSION=r-v0.0.4 ARG WEAVE_ROUTER_SHA256=15d55366dbcc33c8a9fd3ca5d3e53256b9184966bc1a59e2c88ed524660382ea @@ -89,7 +89,7 @@ RUN set -eux; \ tar -xzf /tmp/cni.tgz -C /tmp/cni ./loopback; \ tar -xzf /tmp/cni-bridge.tgz -C /tmp/cni-bridge; \ install -m 0755 /tmp/cni/loopback /opt/cni/bin/loopback; \ - install -m 0755 /tmp/cni-bridge/rancher-cni-bridge /opt/cni/bin/pasture-bridge-core; \ + install -m 0755 /tmp/cni-bridge/rancher-bridge /opt/cni/bin/pasture-bridge-core; \ chmod 0755 /usr/bin/weave-router; \ rm -rf /tmp/cni.tgz /tmp/cni-bridge.tgz /tmp/cni /tmp/cni-bridge diff --git a/scripts/integration-optional-cni-isolated b/scripts/integration-optional-cni-isolated index 3bc9491..16863b8 100755 --- a/scripts/integration-optional-cni-isolated +++ b/scripts/integration-optional-cni-isolated @@ -38,16 +38,20 @@ echo 'PER_HOST_CNI=pass' # bridge inside this container. A direct address avoids depending on a real # platform Metadata API; the Metadata lookup path has its own source tests. ip link add brflatqa type bridge -ip address add 10.55.244.1/24 dev brflatqa +ip address add 10.55.244.2/24 dev brflatqa ip link set brflatqa up ip netns add qa-flat -export CNI_NETNS=/run/netns/qa-flat 'CNI_ARGS=IgnoreUnknown=1;MACAddress=02:42:37:ff:01:02;IPAddress=10.55.244.2/24' -flat='{"cniVersion":"0.1.0","name":"qa-flat","type":"pasture-bridge","bridge":"brflatqa","bridgeSubnet":"10.55.244.1/24","skipBridgeConfigureIP":true,"skipFastPath":true,"hostNat":false,"ipam":{"type":"flat-cni-ipam","metadataAddress":"169.254.169.250"}}' +export CNI_NETNS=/run/netns/qa-flat 'CNI_ARGS=IgnoreUnknown=1;MACAddress=02:42:37:ff:01:02;IPAddress=10.55.244.3/24' +flat='{"cniVersion":"0.1.0","name":"qa-flat","type":"pasture-bridge","bridge":"brflatqa","bridgeSubnet":"10.55.244.0/24","skipBridgeConfigureIP":true,"skipFastPath":true,"hostNat":false,"ipam":{"type":"flat-cni-ipam","metadataAddress":"169.254.169.250"}}' if ! result=$(CNI_COMMAND=ADD /opt/cni/bin/pasture-bridge <<<"$flat"); then printf 'flat CNI ADD: %s\n' "$result" >&2 exit 1 fi -check_link qa-flat 10.55.244.2 10.55.244.1 +check_link qa-flat 10.55.244.3 10.55.244.2 +if [[ $(ip -4 -o address show dev brflatqa | wc -l) -ne 1 ]] || ! ip -4 address show dev brflatqa | grep -q '10.55.244.2/24'; then + echo 'flat CNI changed the preconfigured bridge address' >&2 + exit 1 +fi CNI_COMMAND=DEL /opt/cni/bin/pasture-bridge <<<"$flat" >/dev/null if ip netns exec qa-flat ip link show eth0 >/dev/null 2>&1; then echo 'flat CNI DEL left eth0 behind' >&2 diff --git a/scripts/integration-two-node-ipsec b/scripts/integration-two-node-ipsec index d469f38..cc50d24 100755 --- a/scripts/integration-two-node-ipsec +++ b/scripts/integration-two-node-ipsec @@ -1,7 +1,7 @@ #!/bin/bash set -euo pipefail -image=${IMAGE:-local/pasturestack/ipsec-vxlan-overlay-network:0.14.35} +image=${IMAGE:-local/pasturestack/ipsec-vxlan-overlay-network:0.14.36} old_image=${OLD_IMAGE:-} startup_image=${old_image:-$image} test_id=$$ diff --git a/scripts/integration-two-node-vxlan b/scripts/integration-two-node-vxlan index d4472a2..61fe656 100755 --- a/scripts/integration-two-node-vxlan +++ b/scripts/integration-two-node-vxlan @@ -1,7 +1,7 @@ #!/bin/bash set -euo pipefail -image=${IMAGE:-local/pasturestack/ipsec-vxlan-overlay-network:0.14.35} +image=${IMAGE:-local/pasturestack/ipsec-vxlan-overlay-network:0.14.36} test_id=$$ network="pasture-vxlan-it-${test_id}" node_a="pasture-vxlan-a-${test_id}" diff --git a/scripts/validate b/scripts/validate index ef2eb87..f84fe5a 100755 --- a/scripts/validate +++ b/scripts/validate @@ -49,7 +49,7 @@ for script in scripts/* package/*.sh package/update-platform-ca; do bash -n "$script" done -version=${VERSION_OVERRIDE:-0.14.35} +version=${VERSION_OVERRIDE:-0.14.36} first=$(mktemp -d) trap 'rm -rf "$first"' EXIT VERSION_OVERRIDE="$version" ./scripts/build