74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Offline Tailworm packaging floor check; never installs or contacts a daemon.
|
|
|
|
Version evidence is supplied explicitly. Passing is only a version-floor result,
|
|
not package provenance, running-daemon, configuration, or fleet acceptance.
|
|
"""
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
|
|
MINIMUM = (1, 104, 4)
|
|
BULLETINS = [f"TS-2026-{n:03d}" for n in range(14, 18)]
|
|
|
|
|
|
def release_version(value):
|
|
# Only stable upstream releases and numeric Debian packaging revisions.
|
|
# Do not turn rc/dev/dirty builds or unknown vendor backports into passes.
|
|
match = re.fullmatch(r"v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-[1-9][0-9]*)?", value)
|
|
if not match:
|
|
return None
|
|
version = tuple(map(int, match.groups()))
|
|
if version[0] != 1 or version[1] % 2:
|
|
return None
|
|
return version
|
|
|
|
|
|
def assess(value):
|
|
version = release_version(value)
|
|
return {
|
|
"version": value,
|
|
"status": "unknown-version" if version is None else (
|
|
"meets-security-floor" if version >= MINIMUM else "below-security-floor"
|
|
),
|
|
}
|
|
|
|
|
|
def check_manifest(path):
|
|
import yaml
|
|
|
|
app = yaml.safe_load(Path(path).read_text())["app"]
|
|
if app["id"] != "tailscale":
|
|
raise ValueError("expected tailscale app")
|
|
version = str(app["version"])
|
|
result = assess(version)
|
|
image = app["container"]["image"]
|
|
# Require a reviewable exact release tag; digest may additionally pin it.
|
|
match = re.fullmatch(r"[^\s@]+:v(1\.[0-9]+\.[0-9]+)(?:@sha256:[0-9a-f]{64})?", image)
|
|
if not match or match[1] != version:
|
|
result["status"] = "unversioned-or-mismatched-image"
|
|
result.update(manifest=str(path), image=image)
|
|
return result
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--version", action="append", default=[], help="explicit package/binary version; repeat for CLI and daemon")
|
|
parser.add_argument("--manifest", type=Path)
|
|
args = parser.parse_args()
|
|
if not args.version and args.manifest is None:
|
|
parser.error("supply --version and/or --manifest")
|
|
results = [assess(value) for value in args.version]
|
|
if args.manifest is not None:
|
|
try:
|
|
results.append(check_manifest(args.manifest))
|
|
except (OSError, ValueError, KeyError, TypeError, ImportError) as error:
|
|
results.append({"status": "manifest-error", "error": str(error)})
|
|
print(json.dumps({"scope": "version-floor-only", "minimum": "1.104.4", "bulletins": BULLETINS, "results": results}, indent=2))
|
|
return 0 if all(row["status"] == "meets-security-floor" for row in results) else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|