railiance-platform/scripts/prove-backup-publisher-prefixes.py

99 lines
2.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Prove each new publisher can abort its own upload and cannot write elsewhere.
No completed object is left behind. The receipt contains status codes only.
"""
from __future__ import annotations
import json
import subprocess
from pathlib import Path
import boto3
from botocore.exceptions import ClientError
RECEIPT = Path("/home/worsch/.local/state/railiance/receipts/backup-publisher-prefix-proof.json")
BUCKET = "railiance-platform-pg-backup"
PROFILES = {
"coulomb-social": "platform-pg/application-archives/coulomb-social/.policy-probe",
"email-connect": "platform-pg/application-archives/email-connect/.policy-probe",
}
FOREIGN = "platform-pg/apps-pg/.policy-probe"
def kv(path):
proc = subprocess.run(["bao", "kv", "get", "-format=json", path], capture_output=True)
if proc.returncode:
raise SystemExit(81)
return json.loads(proc.stdout)["data"]["data"]
def client(values):
return boto3.client(
"s3",
region_name="nl-ams",
endpoint_url="https://s3.nl-ams.scw.cloud",
aws_access_key_id=values["AWS_ACCESS_KEY_ID"],
aws_secret_access_key=values["AWS_SECRET_ACCESS_KEY"],
)
def code(exc):
return exc.response.get("Error", {}).get("Code", "error")
def attempt(action):
try:
action()
return "ok"
except ClientError as exc:
return code(exc)
def open_or_deny(remote, key):
try:
upload = remote.create_multipart_upload(Bucket=BUCKET, Key=key)
except ClientError as exc:
return code(exc)
remote.abort_multipart_upload(Bucket=BUCKET, Key=key, UploadId=upload["UploadId"])
return "opened_then_aborted"
def main():
rows = []
for name, own_key in PROFILES.items():
remote = client(kv(f"platform/workloads/{name}/backup-primary"))
own = "error"
try:
upload = remote.create_multipart_upload(Bucket=BUCKET, Key=own_key)
remote.abort_multipart_upload(
Bucket=BUCKET, Key=own_key, UploadId=upload["UploadId"]
)
own = "aborted"
except ClientError as exc:
own = code(exc)
sibling_key = next(key for other, key in PROFILES.items() if other != name)
foreign = open_or_deny(remote, FOREIGN)
sibling = open_or_deny(remote, sibling_key)
delete = attempt(lambda key=own_key: remote.delete_object(Bucket=BUCKET, Key=key))
rows.append({
"application": name,
"own_multipart": own,
"foreign_multipart": foreign,
"sibling_multipart": sibling,
"delete_object": delete,
})
RECEIPT.parent.mkdir(mode=0o700, exist_ok=True)
RECEIPT.write_text(json.dumps({"proofs": rows}, indent=2) + "\n")
RECEIPT.chmod(0o600)
if __name__ == "__main__":
try:
main()
except SystemExit:
raise
except Exception:
raise SystemExit(82) from None