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
566 changes: 566 additions & 0 deletions .github/runtime-unfixed.json

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions .github/scripts/enforce-runtime-vulnerabilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Enforce release findings; only exact, reviewed unfixed low/medium entries may pass."""
import argparse
import json
import sys
from pathlib import Path


def violations(report, ledger, repository):
if (not isinstance(report, dict) or report.get("SchemaVersion") != 2
or not isinstance(report.get("Results"), list) or not report["Results"]):
raise ValueError("missing or malformed Trivy scan results")
for result in report["Results"]:
if not isinstance(result, dict) or not isinstance(result.get("Target"), str):
raise ValueError("malformed Trivy result")
for key in ("Vulnerabilities", "Secrets"):
if key in result and result[key] is not None and not isinstance(result[key], list):
raise ValueError("malformed Trivy findings")
if ledger.get("repository") != repository:
raise ValueError("allowlist repository mismatch")
reviewed = {}
for entry in ledger.get("unfixed", []):
key = (entry["id"], entry["package"], entry["version"], entry.get("purl", ""))
if key in reviewed:
raise ValueError("duplicate reviewed finding")
if (entry.get("severity") not in ("LOW", "MEDIUM")
or entry.get("status") != "no_official_fix"
or not entry.get("officialEvidence", "").startswith("https://")
or not entry.get("reason") or not entry.get("checkedAt")):
raise ValueError("incomplete official no-fix evidence")
reviewed[key] = entry
errors = []
for result in report.get("Results") or []:
if result.get("Secrets"):
errors.append("secret findings remain")
for finding in result.get("Vulnerabilities") or []:
severity = finding.get("Severity", "UNKNOWN")
key = (finding.get("VulnerabilityID"), finding.get("PkgName"),
finding.get("InstalledVersion"),
(finding.get("PkgIdentifier") or {}).get("PURL", ""))
entry = reviewed.get(key)
fixed = str(finding.get("FixedVersion") or "").strip()
if (severity not in ("LOW", "MEDIUM") or fixed
or entry is None or entry["severity"] != severity):
errors.append("%s %s@%s severity=%s fixed=%s" % (
key[0], key[1], key[2], severity, fixed or "none"))
return errors


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--repository", required=True)
args = parser.parse_args()
failures = violations(json.load(sys.stdin),
json.loads((Path(__file__).resolve().parents[1] / "runtime-unfixed.json").read_text(encoding="utf-8")), args.repository)
if failures:
raise SystemExit("Release vulnerability policy failed:\n" + "\n".join(failures))
print("RELEASE_VULNERABILITY_POLICY_OK")
77 changes: 77 additions & 0 deletions .github/scripts/test_runtime_vulnerability_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import copy
import importlib.util
import pathlib
import unittest

PATH = pathlib.Path(__file__).with_name('enforce-runtime-vulnerabilities.py')
SPEC = importlib.util.spec_from_file_location('gate', PATH)
GATE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(GATE)
REPO = 'PastureStack/network-policy-manager'


class RuntimePolicy(unittest.TestCase):
def setUp(self):
self.finding = {'VulnerabilityID': 'TEST-1', 'PkgName': 'lib', 'InstalledVersion': '1',
'Severity': 'MEDIUM', 'FixedVersion': '',
'PkgIdentifier': {'PURL': 'pkg:test/lib@1'}}
self.report = {'SchemaVersion': 2, 'Results': [{'Target': 'fixture', 'Vulnerabilities': [self.finding]}]}
self.ledger = {'repository': REPO, 'unfixed': [{'id': 'TEST-1', 'package': 'lib',
'version': '1', 'severity': 'MEDIUM', 'purl': 'pkg:test/lib@1',
'status': 'no_official_fix', 'officialEvidence': 'https://fixture.invalid/advisory',
'reason': 'Test fixture only', 'checkedAt': '2026-10-23'}]}

def test_exact_reviewed_unfixed_medium_passes(self):
self.assertEqual(GATE.violations(self.report, self.ledger, REPO), [])

def test_clean_scan_passes(self):
self.assertEqual(GATE.violations({'SchemaVersion': 2, 'Results': [{'Target': 'fixture'}]},
{'repository': REPO, 'unfixed': []}, REPO), [])

def test_missing_or_malformed_scan_fails_closed(self):
for report in ({}, {'Results': []}, {'SchemaVersion': 2, 'Results': []},
{'SchemaVersion': 2, 'Results': [{}]},
{'SchemaVersion': 2, 'Results': [{'Target': 'fixture', 'Vulnerabilities': {}}]}):
with self.assertRaises(ValueError):
GATE.violations(report, self.ledger, REPO)

def test_unregistered_medium_blocks(self):
self.ledger['unfixed'] = []
self.assertTrue(GATE.violations(self.report, self.ledger, REPO))

def test_fixed_medium_blocks(self):
self.finding['FixedVersion'] = '2'
self.assertTrue(GATE.violations(self.report, self.ledger, REPO))

def test_fixed_low_blocks(self):
self.finding['Severity'] = 'LOW'
self.ledger['unfixed'][0]['severity'] = 'LOW'
self.finding['FixedVersion'] = '2'
self.assertTrue(GATE.violations(self.report, self.ledger, REPO))

def test_high_critical_and_unknown_always_block(self):
for severity in ('HIGH', 'CRITICAL', 'UNKNOWN'):
self.finding['Severity'] = severity
self.assertTrue(GATE.violations(self.report, self.ledger, REPO))

def test_version_or_purl_drift_blocks(self):
for key in ('version', 'purl'):
ledger = copy.deepcopy(self.ledger)
ledger['unfixed'][0][key] = 'different'
self.assertTrue(GATE.violations(self.report, ledger, REPO))

def test_secrets_always_block(self):
self.report['Results'][0]['Secrets'] = [{'RuleID': 'test'}]
self.assertTrue(GATE.violations(self.report, self.ledger, REPO))

def test_repository_drift_fails_closed(self):
with self.assertRaises(ValueError):
GATE.violations(self.report, self.ledger, 'another/repo')

def test_incomplete_evidence_fails_closed(self):
del self.ledger['unfixed'][0]['checkedAt']
with self.assertRaises(ValueError):
GATE.violations(self.report, self.ledger, REPO)

if __name__ == '__main__':
unittest.main()
46 changes: 0 additions & 46 deletions .github/workflows/supply-chain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,49 +377,3 @@ jobs:
done
docker builder prune --all --force >/dev/null
rm -rf bin dist evidence "$trivy_cache"
publish:
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'
needs: verify
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: write
actions: read
packages: write
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: v0.8.20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Publish only this run's verified immutable runtime
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
test "$(git rev-parse "refs/tags/$RELEASE_TAG^{}")" = "$GITHUB_SHA"
gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" --name "security-runtime-$GITHUB_SHA" --dir dist/security-runtime
test "$(cat dist/security-runtime/source-revision.txt)" = "$GITHUB_SHA"
(cd dist/security-runtime && sha256sum --check SHA256SUMS)
gzip -dc dist/security-runtime/images.tar.gz | docker load
printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
trap 'docker logout ghcr.io >/dev/null 2>&1 || true' EXIT
: > dist/security-runtime/registry-images.jsonl
local_image='ghcr.io/pasturestack/resource-scheduler:v0.8.20'
remote_image="ghcr.io/pasturestack/resource-scheduler:$RELEASE_TAG"
if docker manifest inspect "$remote_image" >/dev/null 2>"$RUNNER_TEMP/manifest-error"; then
echo "Refusing to replace an existing immutable image tag: $remote_image" >&2; exit 1
else
grep -Eq 'manifest unknown|no such manifest' "$RUNNER_TEMP/manifest-error"
fi
docker tag "$local_image" "$remote_image"
docker push "$remote_image" | tee "$RUNNER_TEMP/push-log"
pushed=$(awk '/digest: sha256:/{for(i=1;i<=NF;i++) if($i=="digest:") print $(i+1)}' "$RUNNER_TEMP/push-log")
readback=$(docker buildx imagetools inspect "$remote_image" | awk '$1=="Digest:"{print $2; exit}')
test "$pushed" = "$readback"
[[ "$readback" =~ ^sha256:[0-9a-f]{64}$ ]]
jq -n --arg image "$remote_image" --arg digest "$readback" --arg source "$GITHUB_SHA" '{image:$image,digest:$digest,source:$source}' >> dist/security-runtime/registry-images.jsonl
sha256sum dist/security-runtime/registry-images.jsonl > dist/security-runtime/registry-images.sha256
gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REPOSITORY $RELEASE_TAG" --notes "Official security rebuild from $GITHUB_SHA. The release contains the exact tested/scanned runtime archive, SBOMs, and registry digest readbacks. No deployment is included." dist/security-runtime/*
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# PastureStack Resource Scheduler

The current published release is [v0.8.20](https://github.com/PastureStack/resource-scheduler/releases/tag/v0.8.20),
produced from `35d3a67c62df4b22d7167da3d73dd9dbc4905e66` by the existing
[release workflow](https://github.com/PastureStack/resource-scheduler/actions/runs/39961862641).
Its runtime digest is `sha256:e5a389273344585fca5c4513060e5289b30ca13046d8ec286b8563952f1325d4`.
Only `release.yml` publishes; `supply-chain.yml` retains verification evidence
without a second publisher. Later source changes do not replace released bytes.

Resource Scheduler is the control-plane daemon that tracks host capacity, labels, and host-port reservations. It consumes the dated metadata API, handles scheduling events, ranks eligible hosts, reserves and releases resources, and exposes the health endpoint used by the infrastructure catalog.

PastureStack is an independent community effort to preserve, audit, and modernize the Rancher 1.6 ecosystem. It is not affiliated with or endorsed by Rancher Labs or SUSE.
Expand Down Expand Up @@ -86,3 +93,13 @@ The inherited project remains licensed under [Apache License 2.0](LICENSE). Past
## October 2026 security maintenance

Maintained Go builds select the patched Go 1.27.3 compiler without raising the existing Go language requirement. Ubuntu build/runtime stages apply the checksum-pinned `ca-certificates 20260816ubuntu0.26.04.1` security package after their frozen APT baseline; the final trust store and installed version are verified. Release artifacts must pass the repository's existing checks before publication.

## Full-severity release policy

Release verification retains the complete runtime vulnerability report. Every
finding with an official fix blocks release, regardless of severity. Unknown
findings block unless a separately reviewed, exact not-affected statement is
proved by the actual product dependency graph. LOW/MEDIUM findings without an
official fix require the repository's exact CVE/package/version/PURL review ledger;
new or changed findings fail closed. This policy-only update does not replace any
already published immutable artifact.
3 changes: 3 additions & 0 deletions scripts/assess-runtime-vulnerabilities
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ report=${1:?usage: assess-runtime-vulnerabilities REPORT_JSON OUTPUT_DIR SUBJECT
output_dir=${2:?usage: assess-runtime-vulnerabilities REPORT_JSON OUTPUT_DIR SUBJECT}
subject=${3:?usage: assess-runtime-vulnerabilities REPORT_JSON OUTPUT_DIR SUBJECT}

repo_root=$(cd "$(dirname "$0")/.." && pwd)
python3 "$repo_root/.github/scripts/enforce-runtime-vulnerabilities.py" --repository PastureStack/resource-scheduler < "$report"

mkdir -p "${output_dir}"

total=$(jq '[.Results[]?.Vulnerabilities[]?] | length' "${report}")
Expand Down
Loading