|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Apply IAM policy to daylily-service user for zebra_day DynamoDB access. |
| 4 | +""" |
| 5 | +import json |
| 6 | +import sys |
| 7 | +import time |
| 8 | + |
| 9 | +try: |
| 10 | + import boto3 |
| 11 | + from botocore.exceptions import ClientError |
| 12 | +except ImportError: |
| 13 | + print("ERROR: boto3 is not installed. Install with: pip install boto3") |
| 14 | + sys.exit(1) |
| 15 | + |
| 16 | +POLICY_FILE = "iam-policy-daylily-service-zebra-day.json" |
| 17 | +POLICY_NAME = "ZebraDayDynamoDBAccess" |
| 18 | +USER_NAME = "daylily-service" |
| 19 | +ACCOUNT_ID = "108782052779" |
| 20 | + |
| 21 | + |
| 22 | +def main(): |
| 23 | + # Get AWS profile from command line or environment |
| 24 | + profile = sys.argv[1] if len(sys.argv) > 1 else None |
| 25 | + |
| 26 | + # Load policy document |
| 27 | + try: |
| 28 | + with open(POLICY_FILE, "r") as f: |
| 29 | + policy_document = json.load(f) |
| 30 | + except FileNotFoundError: |
| 31 | + print(f"ERROR: Policy file '{POLICY_FILE}' not found") |
| 32 | + sys.exit(1) |
| 33 | + except json.JSONDecodeError as e: |
| 34 | + print(f"ERROR: Invalid JSON in policy file: {e}") |
| 35 | + sys.exit(1) |
| 36 | + |
| 37 | + # Create IAM client |
| 38 | + session_kwargs = {} |
| 39 | + if profile: |
| 40 | + session_kwargs["profile_name"] = profile |
| 41 | + print(f"Using AWS profile: {profile}") |
| 42 | + |
| 43 | + session = boto3.Session(**session_kwargs) |
| 44 | + iam = session.client("iam") |
| 45 | + |
| 46 | + # Step 1: Create the policy |
| 47 | + print(f"Creating IAM policy '{POLICY_NAME}'...") |
| 48 | + policy_arn = f"arn:aws:iam::{ACCOUNT_ID}:policy/{POLICY_NAME}" |
| 49 | + |
| 50 | + try: |
| 51 | + response = iam.create_policy( |
| 52 | + PolicyName=POLICY_NAME, |
| 53 | + PolicyDocument=json.dumps(policy_document), |
| 54 | + Description="Grants zebra_day access to DynamoDB table and S3 backup buckets", |
| 55 | + ) |
| 56 | + print(f"✓ Policy created: {response['Policy']['Arn']}") |
| 57 | + policy_arn = response["Policy"]["Arn"] |
| 58 | + except ClientError as e: |
| 59 | + if e.response["Error"]["Code"] == "EntityAlreadyExists": |
| 60 | + print(f"⚠ Policy '{POLICY_NAME}' already exists, using existing policy") |
| 61 | + print(f" ARN: {policy_arn}") |
| 62 | + else: |
| 63 | + print(f"ERROR creating policy: {e}") |
| 64 | + sys.exit(1) |
| 65 | + |
| 66 | + # Step 2: Attach policy to user |
| 67 | + print(f"\nAttaching policy to user '{USER_NAME}'...") |
| 68 | + try: |
| 69 | + iam.attach_user_policy( |
| 70 | + UserName=USER_NAME, |
| 71 | + PolicyArn=policy_arn, |
| 72 | + ) |
| 73 | + print(f"✓ Policy attached to user '{USER_NAME}'") |
| 74 | + except ClientError as e: |
| 75 | + if e.response["Error"]["Code"] == "NoSuchEntity": |
| 76 | + print(f"ERROR: User '{USER_NAME}' does not exist") |
| 77 | + sys.exit(1) |
| 78 | + else: |
| 79 | + print(f"ERROR attaching policy: {e}") |
| 80 | + sys.exit(1) |
| 81 | + |
| 82 | + # Step 3: Verify attachment |
| 83 | + print(f"\nVerifying policy attachment...") |
| 84 | + try: |
| 85 | + response = iam.list_attached_user_policies(UserName=USER_NAME) |
| 86 | + attached_policies = response.get("AttachedPolicies", []) |
| 87 | + |
| 88 | + found = False |
| 89 | + for policy in attached_policies: |
| 90 | + if policy["PolicyName"] == POLICY_NAME: |
| 91 | + found = True |
| 92 | + print(f"✓ Policy '{POLICY_NAME}' is attached to '{USER_NAME}'") |
| 93 | + break |
| 94 | + |
| 95 | + if not found: |
| 96 | + print(f"⚠ WARNING: Policy not found in attached policies list") |
| 97 | + |
| 98 | + print(f"\nAll attached policies for '{USER_NAME}':") |
| 99 | + for policy in attached_policies: |
| 100 | + print(f" - {policy['PolicyName']} ({policy['PolicyArn']})") |
| 101 | + except ClientError as e: |
| 102 | + print(f"ERROR verifying attachment: {e}") |
| 103 | + sys.exit(1) |
| 104 | + |
| 105 | + # Step 4: Wait for IAM propagation |
| 106 | + print(f"\n⏳ Waiting 60 seconds for IAM policy propagation...") |
| 107 | + time.sleep(60) |
| 108 | + print("✓ Wait complete") |
| 109 | + |
| 110 | + # Step 5: Test DynamoDB access |
| 111 | + print(f"\nTesting DynamoDB access to 'zebra-day-config' in us-west-2...") |
| 112 | + try: |
| 113 | + dynamodb = session.client("dynamodb", region_name="us-west-2") |
| 114 | + response = dynamodb.describe_table(TableName="zebra-day-config") |
| 115 | + print(f"✓ Successfully accessed table 'zebra-day-config'") |
| 116 | + print(f" Status: {response['Table']['TableStatus']}") |
| 117 | + print(f" Items: {response['Table']['ItemCount']}") |
| 118 | + except ClientError as e: |
| 119 | + if e.response["Error"]["Code"] == "AccessDeniedException": |
| 120 | + print(f"⚠ WARNING: Still getting AccessDeniedException") |
| 121 | + print(f" This may indicate:") |
| 122 | + print(f" 1. IAM propagation needs more time (wait another 30-60s)") |
| 123 | + print(f" 2. AWS credentials are not using the 'daylily-service' user") |
| 124 | + print(f" 3. Additional permissions are needed") |
| 125 | + elif e.response["Error"]["Code"] == "ResourceNotFoundException": |
| 126 | + print(f"⚠ WARNING: Table 'zebra-day-config' does not exist in us-west-2") |
| 127 | + print(f" Run: zday dynamo init --region us-west-2") |
| 128 | + else: |
| 129 | + print(f"ERROR testing DynamoDB access: {e}") |
| 130 | + except Exception as e: |
| 131 | + print(f"ERROR: {e}") |
| 132 | + |
| 133 | + print(f"\n{'='*60}") |
| 134 | + print(f"IAM policy setup complete!") |
| 135 | + print(f"{'='*60}") |
| 136 | + print(f"\nNext steps:") |
| 137 | + print(f"1. Test the GUI backend switch at https://localhost:8118/config") |
| 138 | + print(f"2. If still getting errors, wait another 30-60 seconds for IAM propagation") |
| 139 | + print(f"3. Verify AWS credentials are using the 'daylily-service' user:") |
| 140 | + print(f" aws sts get-caller-identity") |
| 141 | + |
| 142 | + |
| 143 | +if __name__ == "__main__": |
| 144 | + main() |
| 145 | + |
0 commit comments