The Challenge: Replacing Wildcards with Granular PermissionsTo implement least privilege, you must know exactly which API calls your application makes. Guessing leads to application downtime, while wildcards lead to security vulnerabilities.The Solution: Automated Policy Generation via AWS CLIAWS CloudTrail logs every API call. IAM Access Analyzer can digest these logs and output a perfectly tailored JSON IAM policy containing only the actions actually invoked by a role over a specific timeframe. Here is the automation script using the AWS CLI.#!/bin/bashROLE_ARN="arn:aws:iam::123456789012:role/OverPermissiveAppRole"TRAIL_ARN="arn:aws:cloudtrail:us-east-1:123456789012:trail/management-events"START_TIME=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)# Step 1: Initiate Policy GenerationJOB_ID=$(aws accessanalyzer start-policy-generation \ --policy-generation-details "principalArn=$ROLE_ARN" \ --cloud-trail-details "accessRole=arn:aws:iam::123456789012:role/AnalyzerRole,startTime=$START_TIME,endTime=$END_TIME,trails=[$TRAIL_ARN]" \ --query 'jobId' --output text)echo "Started job: $JOB_ID. Waiting for completion..."# Step 2: Poll for Completionwhile true; do STATUS=$(aws accessanalyzer get-generated-policy \ --job-id "$JOB_ID" \ --query 'jobDetails.status' --output text) if [ "$STATUS" == "SUCCEEDED" ]; then break; fi if [ "$STATUS" == "FAILED" ]; then echo "Generation failed"; exit 1; fi sleep 10done# Step 3: Retrieve and Save the Granular Policyaws accessanalyzer get-generated-policy \ --job-id "$JOB_ID" \ --include-resource-placeholders \ --query 'generatedPolicyResult.generatedPolicies[0].policy' \ --output json > least_privilege_policy.jsonecho "Policy saved to least_privilege_policy.json"Script Execution AnalysisLet's break down the mechanics of this automated CloudOps workflow:START_TIME and END_TIME: We dynamically calculate a 7-day lookback window. This ensures we capture a full cycle of application behavior, including background cron jobs or weekly batch processing tasks.start-policy-generation: This API call commands Access Analyzer to ingest CloudTrail data for the specific principalArn. It requires a dedicated accessRole that grants the Analyzer service read access to your CloudTrail S3 bucket.while true; do ... sleep 10: Policy generation is an asynchronous, compute-intensive background process on AWS's side. This loop implements exponential backoff (simplified here to 10s) to poll the job state until it transitions to SUCCEEDED.--include-resource-placeholders: A crucial flag. While Access Analyzer identifies the API actions (e.g., s3:GetObject), it cannot always infer the exact ARN syntax for specific resources. This flag injects placeholder values (like ${ResourceName}) into the JSON output, prompting the security engineer to manually scope the policy down to specific bucket ARNs or table names before deployment.--query 'generatedPolicyResult...policy': Uses JMESPath to extract only the raw JSON IAM document from the nested API response, piping it cleanly into a file ready for Terraform or CloudFormation ingestion.By executing this workflow periodically, you can confidently strip away wildcards and maintain mathematically provable least privilege in your cloud environments.