78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Verify OCI layout emitted by make-thin-v1.sh when OCI=1.
|
|
Checks:
|
|
1) oci-layout exists and version is 1.0.0
|
|
2) index.json manifest digest/size match manifest.json hash/size
|
|
3) manifest.json references config/layers present in blobs with matching sha256 and size
|
|
|
|
Usage:
|
|
python scripts/mirror/verify_oci_layout.py out/mirror/thin/oci
|
|
|
|
Exit 0 on success, non-zero on failure with message.
|
|
"""
|
|
import hashlib, json, pathlib, sys
|
|
|
|
def sha256(path: pathlib.Path) -> str:
|
|
h = hashlib.sha256()
|
|
with path.open('rb') as f:
|
|
for chunk in iter(lambda: f.read(8192), b''):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
def main():
|
|
if len(sys.argv) != 2:
|
|
print(__doc__)
|
|
sys.exit(2)
|
|
root = pathlib.Path(sys.argv[1])
|
|
layout = root / "oci-layout"
|
|
index = root / "index.json"
|
|
manifest = root / "manifest.json"
|
|
if not layout.exists() or not index.exists() or not manifest.exists():
|
|
raise SystemExit("missing oci-layout/index.json/manifest.json")
|
|
|
|
layout_obj = json.loads(layout.read_text())
|
|
if layout_obj.get("imageLayoutVersion") != "1.0.0":
|
|
raise SystemExit("oci-layout version not 1.0.0")
|
|
|
|
idx_obj = json.loads(index.read_text())
|
|
if not idx_obj.get("manifests"):
|
|
raise SystemExit("index.json manifests empty")
|
|
man_digest = idx_obj["manifests"][0]["digest"]
|
|
man_size = idx_obj["manifests"][0]["size"]
|
|
|
|
actual_man_sha = sha256(manifest)
|
|
if man_digest != f"sha256:{actual_man_sha}":
|
|
raise SystemExit(f"manifest digest mismatch: {man_digest} vs sha256:{actual_man_sha}")
|
|
if man_size != manifest.stat().st_size:
|
|
raise SystemExit("manifest size mismatch")
|
|
|
|
man_obj = json.loads(manifest.read_text())
|
|
blobs = root / "blobs" / "sha256"
|
|
# config
|
|
cfg_digest = man_obj["config"]["digest"].split(":",1)[1]
|
|
cfg_size = man_obj["config"]["size"]
|
|
cfg_path = blobs / cfg_digest
|
|
if not cfg_path.exists():
|
|
raise SystemExit(f"config blob missing: {cfg_path}")
|
|
if cfg_path.stat().st_size != cfg_size:
|
|
raise SystemExit("config size mismatch")
|
|
if sha256(cfg_path) != cfg_digest:
|
|
raise SystemExit("config digest mismatch")
|
|
|
|
for layer in man_obj.get("layers", []):
|
|
ldigest = layer["digest"].split(":",1)[1]
|
|
lsize = layer["size"]
|
|
lpath = blobs / ldigest
|
|
if not lpath.exists():
|
|
raise SystemExit(f"layer blob missing: {lpath}")
|
|
if lpath.stat().st_size != lsize:
|
|
raise SystemExit("layer size mismatch")
|
|
if sha256(lpath) != ldigest:
|
|
raise SystemExit("layer digest mismatch")
|
|
|
|
print("OK: OCI layout verified")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|