s3-bucket-policy-and-cors

code_execution · unverified · null · json · download .py

AWS region

import sys
import os
import subprocess
import time
import urllib.request
import json

# ─────────────────────────────────────────
# PRE_EXECUTION
# ─────────────────────────────────────────

for attempt in range(2):
    try:
        req = urllib.request.Request(
            "https://checklist.day/api/registry/boto3",
            headers={"User-Agent": "checklist-agent/1.0"}
        )
        with urllib.request.urlopen(req, timeout=10) as resp:
            registry = json.loads(resp.read())
            break
    except Exception as e:
        if attempt == 1:
            print(f"ABORT: registry unreachable — {e}")
            sys.exit(1)
        time.sleep(2)

warnings = registry.get("warnings", [])
if warnings:
    print("[boto3] WARNINGS:")
    for w in warnings if isinstance(warnings, list) else [warnings]:
        print(f"  ⚠ {w}")

# ─────────────────────────────────────────
# EXECUTION
# ─────────────────────────────────────────

subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "boto3>=1.26.0"])

import boto3
from botocore.exceptions import ClientError

AWS_ACCESS_KEY_ID     = os.environ.get("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
AWS_REGION            = os.environ.get("AWS_REGION", "us-east-1")
S3_BUCKET             = os.environ.get("S3_BUCKET")

if not AWS_ACCESS_KEY_ID:
    print("ABORT: AWS_ACCESS_KEY_ID not set"); sys.exit(1)
if not AWS_SECRET_ACCESS_KEY:
    print("ABORT: AWS_SECRET_ACCESS_KEY not set"); sys.exit(1)
if not S3_BUCKET:
    print("ABORT: S3_BUCKET not set"); sys.exit(1)

client = boto3.client(
    "s3",
    aws_access_key_id=AWS_ACCESS_KEY_ID,
    aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
    region_name=AWS_REGION,
)

# 1. CORS Configuration
# FOOTGUN: boto3 uses dict with CORSRules key — NOT raw XML
# FOOTGUN: AllowedMethods must be a list of strings, not a single string
cors_config = {
    "CORSRules": [
        {
            "AllowedHeaders": ["*"],
            "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
            "AllowedOrigins": ["https://checklist.day", "http://localhost:3000"],
            "ExposeHeaders":  ["ETag"],
            "MaxAgeSeconds":  3600,
        }
    ]
}

cors_set_ok   = False
cors_verified = False

try:
    client.put_bucket_cors(Bucket=S3_BUCKET, CORSConfiguration=cors_config)
    cors_set_ok = True
    print(f"  CORS config applied")

    # Verify readback
    response     = client.get_bucket_cors(Bucket=S3_BUCKET)
    rules        = response.get("CORSRules", [])
    cors_verified = (
        len(rules) > 0 and
        "GET" in rules[0].get("AllowedMethods", []) and
        "https://checklist.day" in rules[0].get("AllowedOrigins", [])
    )
    print(f"  CORS verified: {cors_verified} ({len(rules)} rule(s))")

    # Cleanup CORS
    client.delete_bucket_cors(Bucket=S3_BUCKET)

except ClientError as e:
    print(f"  CORS error: {e.response['Error']['Code']}: {e.response['Error']['Message']}")

# 2. Bucket Policy
# FOOTGUN: policy must be a JSON string, not a dict
policy_set_ok   = False
policy_verified = False

policy_dict = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid":       "ChecklistReadOnly",
            "Effect":    "Deny",
            "Principal": "*",
            "Action":    "s3:DeleteBucket",
            "Resource":  f"arn:aws:s3:::{S3_BUCKET}",
        }
    ],
}

try:
    client.put_bucket_policy(
        Bucket=S3_BUCKET,
        Policy=json.dumps(policy_dict),  # FOOTGUN: must be string
    )
    policy_set_ok = True
    print(f"  bucket policy applied")

    # Verify readback
    response        = client.get_bucket_policy(Bucket=S3_BUCKET)
    returned_policy = json.loads(response["Policy"])
    policy_verified = "Statement" in returned_policy and len(returned_policy["Statement"]) > 0
    print(f"  policy verified: {policy_verified}")

    # Cleanup policy
    client.delete_bucket_policy(Bucket=S3_BUCKET)

except ClientError as e:
    print(f"  policy error: {e.response['Error']['Code']}: {e.response['Error']['Message']}")

# ─────────────────────────────────────────
# POST_EXECUTION
# ─────────────────────────────────────────

assert cors_set_ok, "FAIL: CORS config could not be applied"
assert cors_verified, "FAIL: CORS config readback did not match"

result = {
    "cors_set_ok":     cors_set_ok,
    "cors_verified":   cors_verified,
    "policy_set_ok":   policy_set_ok,
    "policy_verified": policy_verified,
}
print(json.dumps(result, indent=2))
print("PASS")