Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
347 changes: 347 additions & 0 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

104 changes: 64 additions & 40 deletions .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,30 @@ jobs:
fetch-depth: 0
persist-credentials: false

- name: Install verified Go toolchain
shell: bash
run: |
set -euo pipefail
archive="go${GO_VERSION}.linux-amd64.tar.gz"
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
--output "/tmp/${archive}" "https://go.dev/dl/${archive}"
printf '%s %s\n' "$GO_LINUX_AMD64_SHA256" "/tmp/${archive}" | sha256sum -c -
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "/tmp/${archive}"
printf '/usr/local/go/bin\n' >> "$GITHUB_PATH"
/usr/local/go/bin/go version | grep -Fx "go version go${GO_VERSION} linux/amd64"

- name: Verify history, version, privacy, and locks
shell: bash
run: |
set -euo pipefail
test -z "$(git status --porcelain)"
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
test "$(git rev-list --count 721d6ddb2f0a4faa11c9a50e75ed28e1f7cad317..HEAD)" -eq 1
test "$(git rev-list --count --merges 721d6ddb2f0a4faa11c9a50e75ed28e1f7cad317..HEAD)" -eq 0
reviewed_base=721d6ddb2f0a4faa11c9a50e75ed28e1f7cad317
test "$(git cat-file -t "$reviewed_base")" = commit
git merge-base --is-ancestor "$reviewed_base" HEAD
test "$(git rev-list --count "$reviewed_base"..HEAD)" -ge 1
test "$(git rev-list --count --merges "$reviewed_base"..HEAD)" -eq 0
source scripts/version
test "$VERSION" = "$CANDIDATE_VERSION"
go list -mod=vendor ./... >/dev/null
Expand All @@ -59,19 +75,6 @@ jobs:
sha256sum go.mod go.sum vendor/modules.txt ubuntu-apt.lock Dockerfile.dapper package/Dockerfile \
security/dapper-linux-libc-dev.cves > evidence/source-locks.sha256

- name: Install verified Go toolchain
shell: bash
run: |
set -euo pipefail
archive="go${GO_VERSION}.linux-amd64.tar.gz"
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
--output "/tmp/${archive}" "https://go.dev/dl/${archive}"
printf '%s %s\n' "$GO_LINUX_AMD64_SHA256" "/tmp/${archive}" | sha256sum -c -
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "/tmp/${archive}"
printf '/usr/local/go/bin\n' >> "$GITHUB_PATH"
/usr/local/go/bin/go version | grep -Fx "go version go${GO_VERSION} linux/amd64"

- name: Validate, race-test, integrate metadata, and build reproducibly
shell: bash
run: |
Expand Down Expand Up @@ -102,6 +105,14 @@ jobs:
test "$(cat dist/images)" = "$IMAGE"
test "$(docker image inspect "$IMAGE" --format '{{index .Config.Labels "org.opencontainers.image.version"}}')" = "$CANDIDATE_VERSION"
test "$(docker image inspect "$IMAGE" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$GITHUB_SHA"
# All five published companion binaries ship in the runtime image.
# Do not regress to a vulnerable Go toolchain even if the CVE DB lags.
for companion in metadata-cni-ipam per-host-subnet host-local-cni-ipam flat-cni-ipam mount-propagation; do
built_with="$(go version -m "package/$companion" | awk 'NR == 1 {print $NF}')"
[[ "$built_with" =~ ^go[0-9]+\.[0-9]+\.[0-9]+$ ]]
test "$(printf '%s\n%s\n' go1.26.6 "$built_with" | sort -V | head -n 1)" = go1.26.6
printf '%s\t%s\n' "$companion" "$built_with" >> evidence/companion-go-versions.tsv
done

- name: Record exact build and runtime package inventories
shell: bash
Expand All @@ -118,7 +129,7 @@ jobs:
/licenses/IPSEC-VXLAN-OVERLAY-RUNTIME-UBUNTU-APT-PACKAGES.tsv \
> evidence/runtime-ubuntu-apt-packages.tsv
docker run --rm --entrypoint docker "$dapper_image" --version \
| grep -F 'Docker version 29.7.2' \
| grep -F 'Docker version 29.8.0' \
> evidence/docker-cli-version.txt
printf 'DAPPER_IMAGE=%s\n' "$dapper_image" >> "$GITHUB_ENV"

Expand Down Expand Up @@ -216,12 +227,23 @@ jobs:
awk -v purl="$dapper_purl" 'NF {print $0 "\t" purl}' \
security/dapper-linux-libc-dev.cves | LC_ALL=C sort -u \
> evidence/dapper-reviewed.tsv
test -s evidence/dapper-critical-high.tsv
test "$(wc -l < evidence/dapper-critical-high.tsv)" -eq \
"$(wc -l < security/dapper-linux-libc-dev.cves)"
diff -u evidence/dapper-reviewed.tsv evidence/dapper-critical-high.tsv
test "$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL" or .Severity == "HIGH")] | length' evidence/dapper-security.json)" -eq \
comm -12 evidence/dapper-critical-high.tsv evidence/dapper-reviewed.tsv \
> evidence/dapper-reviewed-matched.tsv
comm -23 evidence/dapper-critical-high.tsv evidence/dapper-reviewed.tsv \
> evidence/dapper-unreviewed.tsv
comm -13 evidence/dapper-critical-high.tsv evidence/dapper-reviewed.tsv \
> evidence/dapper-reviewed-absent.tsv
test "$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL" or .Severity == "HIGH")] | length' evidence/dapper-security.json)" -ge \
"$(wc -l < evidence/dapper-critical-high.tsv)"
# Dapper is a separate, disposable builder-compatibility image. Its
# unreviewed findings remain explicit evidence, never product VEX.
# A non-header finding in its tooling still blocks this gate.
if awk -F '\t' -v purl="$dapper_purl" \
'$2 != purl {print; unexpected=1} END {exit !unexpected}' \
evidence/dapper-unreviewed.tsv; then
echo 'Unreviewed non-header vulnerability in Dapper tooling' >&2
exit 1
fi
grep -Fx $'linux-libc-dev:amd64\t7.0.0-29.29' \
evidence/dapper-ubuntu-apt-packages.tsv >/dev/null
if grep -Eq '^linux-(image|modules)([-:]|[[:space:]])' \
Expand All @@ -237,7 +259,7 @@ jobs:
test -z "$(find "$dapper_rootfs" -type f \
\( -path '*/boot/vmlinuz*' -o -path '*/lib/modules/*' -o -path '*/usr/lib/modules/*' \) \
-print -quit)"
jq -Rn \
cut -f1 evidence/dapper-reviewed-matched.tsv | jq -Rn \
--arg purl "$dapper_purl" \
--arg timestamp "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--arg impact 'This build-only package contains Linux user-space API headers, not the vulnerable kernel implementation. It is used only by the disposable compile and race-test environment and is not copied into the product runtime image.' \
Expand All @@ -254,13 +276,13 @@ jobs:
timestamp:$timestamp,
version:1,
statements:.
}' security/dapper-linux-libc-dev.cves \
}' \
> evidence/dapper.openvex.json
test "$(jq '.statements | length' evidence/dapper.openvex.json)" -eq \
"$(wc -l < evidence/dapper-reviewed.tsv)"
"$(wc -l < evidence/dapper-reviewed-matched.tsv)"
rm -rf -- "$source_tree" "$product_tree" "$dapper_rootfs"

- name: Enforce zero Critical, High, or secrets
- name: Enforce delivered product zero and record disposable builder findings
shell: bash
run: |
set -euo pipefail
Expand All @@ -282,21 +304,25 @@ jobs:
dapper_raw_critical=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' evidence/dapper-security.json)
dapper_raw_high=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' evidence/dapper-security.json)
dapper_reviewed_critical=$(jq -n \
--rawfile pairs evidence/dapper-reviewed.tsv \
--rawfile pairs evidence/dapper-reviewed-matched.tsv \
--slurpfile report evidence/dapper-security.json \
'[($pairs | split("\n")[] | select(length > 0) | split("\t")[0])] as $ids
'[($pairs | split("\n")[] | select(length > 0))] as $pairs_list
| [$report[0].Results[]?.Vulnerabilities[]?
| select(.Severity == "CRITICAL" and (.VulnerabilityID as $id | $ids | index($id)))]
| select(.Severity == "CRITICAL" and
((.VulnerabilityID + "\t" + .PkgIdentifier.PURL) as $pair
| $pairs_list | index($pair) != null))]
| length')
dapper_reviewed_high=$(jq -n \
--rawfile pairs evidence/dapper-reviewed.tsv \
--rawfile pairs evidence/dapper-reviewed-matched.tsv \
--slurpfile report evidence/dapper-security.json \
'[($pairs | split("\n")[] | select(length > 0) | split("\t")[0])] as $ids
'[($pairs | split("\n")[] | select(length > 0))] as $pairs_list
| [$report[0].Results[]?.Vulnerabilities[]?
| select(.Severity == "HIGH" and (.VulnerabilityID as $id | $ids | index($id)))]
| select(.Severity == "HIGH" and
((.VulnerabilityID + "\t" + .PkgIdentifier.PURL) as $pair
| $pairs_list | index($pair) != null))]
| length')
dapper_applicable_critical=$((dapper_raw_critical - dapper_reviewed_critical))
dapper_applicable_high=$((dapper_raw_high - dapper_reviewed_high))
dapper_unreviewed_critical=$((dapper_raw_critical - dapper_reviewed_critical))
dapper_unreviewed_high=$((dapper_raw_high - dapper_reviewed_high))
jq -n \
--arg method 'exact-id-purl-set-match' \
--arg source 'evidence/dapper-security.json' \
Expand All @@ -305,20 +331,18 @@ jobs:
--argjson raw_high "$dapper_raw_high" \
--argjson reviewed_critical "$dapper_reviewed_critical" \
--argjson reviewed_high "$dapper_reviewed_high" \
--argjson applicable_critical "$dapper_applicable_critical" \
--argjson applicable_high "$dapper_applicable_high" \
--argjson unreviewed_critical "$dapper_unreviewed_critical" \
--argjson unreviewed_high "$dapper_unreviewed_high" \
'{method:$method,source_report:$source,vex_document:$vex,
raw:{critical:$raw_critical,high:$raw_high},
reviewed_not_affected:{critical:$reviewed_critical,high:$reviewed_high},
applicable:{critical:$applicable_critical,high:$applicable_high}}' \
unreviewed_not_assessed:{critical:$unreviewed_critical,high:$unreviewed_high}}' \
> evidence/dapper-openvex-review.json
printf 'dapper_secrets=%s\ndapper_raw_critical=%s\ndapper_raw_high=%s\ndapper_applicable_critical=%s\ndapper_applicable_high=%s\n' \
printf 'dapper_secrets=%s\ndapper_raw_critical=%s\ndapper_raw_high=%s\ndapper_unreviewed_critical=%s\ndapper_unreviewed_high=%s\n' \
"$dapper_secrets" "$dapper_raw_critical" "$dapper_raw_high" \
"$dapper_applicable_critical" "$dapper_applicable_high" \
"$dapper_unreviewed_critical" "$dapper_unreviewed_high" \
| tee -a evidence/security-summary.txt
test "$dapper_secrets" -eq 0
test "$dapper_applicable_critical" -eq 0
test "$dapper_applicable_high" -eq 0

- name: Record and upload short-lived evidence
if: always()
Expand Down
5 changes: 5 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ PastureStack names are the public interface for new deployments. A limited set o
- Metadata address environment variable: `PASTURESTACK_METADATA_ADDRESS`
- Debug environment variable: `PASTURESTACK_DEBUG`
- 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. `auto` reads Docker's live nftables or xtables NAT tables in the host namespace; it does not infer a mode from whichever CLI binary happens to be installed.
- CNI log: `/var/log/pasturestack-cni.log`
- Platform CA: `/var/lib/pasturestack/etc/ssl/ca.crt`

Expand All @@ -27,6 +28,10 @@ The following are compatibility adapters, not PastureStack branding:

The default metadata endpoint is the brand-neutral link-local address `http://169.254.169.250/2015-12-19`; it does not depend on an internal DNS alias.

For Docker's native nftables bridge firewall, select `nftables` or let `auto` discover `ip docker-bridges`. The router does not write host firewall rules in this mode: the active network-plugin-manager must manage the overlay subnet's forwarding mark and exclude overlay destinations from its own egress masquerade. Docker must be configured to accept mark `0x1068/0x1068`. An xtables `ACCEPT` rule in a different nftables base chain is not a valid replacement for that NAT exclusion. The router does not alter Docker-owned chains, host FORWARD policy, or legacy kernel modules. If Docker's native table is absent, it chooses the live `iptables-nft` DOCKER NAT chain, or an already-loaded legacy DOCKER NAT chain for an old host; it fails closed if neither is identifiable. An explicit xtables selection is rejected while Docker's native table exists.

The VXLAN router's historical POSTROUTING rule executes only inside its own container network namespace, not in the IPsec host-XFRM namespace. The IPsec firewall selection does not change that independent runtime path.

These identifiers must not be copied into new external APIs. They may be removed only after the server, agent, catalog, and stored environment data no longer emit or reference them.

Vendored Go import paths under `github.com/rancher/*` identify third-party upstream packages and remain for source and license traceability.
4 changes: 2 additions & 2 deletions Dockerfile.dapper
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ ADD --checksum=sha256:6077d27c6b6f8b23590cb01ff877ed8c804a67a5442cc32b5a33da10d2

ARG DAPPER_HOST_ARCH=amd64
ARG GO_VERSION=1.27.0
ARG DOCKER_VERSION=29.7.2
ARG DOCKER_VERSION=29.8.0
ARG GO_LINUX_AMD64_SHA256=675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685
ARG DOCKER_LINUX_AMD64_SHA256=803d433f226db4776e1768fd319fc6c6e4935a456acf84fcc0080818b854bc8f
ARG DOCKER_LINUX_AMD64_SHA256=cc21815cf1e2efed867dc9c8b96b46ffed8ea176ffab32b0aacb54726ded8f25

ENV DEBIAN_FRONTEND=noninteractive
ENV HOST_ARCH=${DAPPER_HOST_ARCH} ARCH=${DAPPER_HOST_ARCH}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ TARGETS := $(shell ls scripts)

DAPPER_IMAGE ?= pasturestack-overlay-network-dapper:ubuntu26
DAPPER_HOST_ARCH ?= amd64
DOCKER_VERSION ?= 29.7.2
DOCKER_VERSION ?= 29.8.0
DOCKER_BUILD_NETWORK ?= host
DAPPER_SOURCE ?= /go/src/github.com/PastureStack/ipsec-vxlan-overlay-network

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ evidence rather than a complete matching Release-and-image publication.

The image is intended to be launched by the PastureStack infrastructure catalog. The IPsec router requires host PID access, `NET_ADMIN`-equivalent privileged access, and the network namespace contract documented in [COMPATIBILITY.md](COMPATIBILITY.md). It is not a standalone control plane or an unprivileged application container.

The release gate rejects Critical/High findings and secrets in the source,
shipped binaries, and runtime image. It scans the disposable Dapper builder
separately and retains its raw findings; only exact, already-reviewed
`linux-libc-dev` header findings receive builder-scoped VEX. New builder
findings remain visible and are not evidence that the shipped runtime is safe.
The Dapper image and its kernel headers are not included in the release image.

Preferred commands inside the image are:

- `start-ipsec.sh` — start the IPsec overlay router.
Expand All @@ -45,6 +52,10 @@ The package build downloads dependencies anonymously, verifies every standalone

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

The catalog IPsec `overlay-router` runs in the host network namespace. Its startup script resolves `PASTURESTACK_FIREWALL_BACKEND=auto` once from the host's live Docker firewall tables and passes the selected value to route synchronization. Operators may explicitly choose `nftables`, `iptables-nft`, or `iptables-legacy`; a mismatched selection fails rather than modifying another backend. The native path never invokes `iptables-legacy` and does not write any host firewall rule. The active network manager is the sole owner of overlay forwarding marks and NAT; Docker's native bridge firewall must accept mark `0x1068/0x1068`. See [COMPATIBILITY.md](COMPATIBILITY.md) for the boundary and migration notes.

## Origin and licensing

The official upstream history and original copyright notices are preserved. See [ORIGIN.md](ORIGIN.md), [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md), and [LICENSE](LICENSE) before redistributing this source or its image.
Expand Down
Loading
Loading