To implement least privilege, you must know exactly which API calls your application makes. Guessing leads to application downtime, while wildcards lead to security vulnerabilities.
AWS 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/bash
ROLE_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 Generation
JOB_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 Completion
while 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 10
done
# Step 3: Retrieve and Save the Granular Policy
aws accessanalyzer get-generated-policy \
--job-id "$JOB_ID" \
--include-resource-placeholders \
--query 'generatedPolicyResult.generatedPolicies[0].policy' \
--output json > least_privilege_policy.json
echo "Policy saved to least_privilege_policy.json"
Let'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.