← Back to Blog

Automating IAM Least Privilege Policies in AWS using Access Analyzer



Automating IAM Least Privilege Policies in AWS using Access Analyzer

Overly permissive IAM roles represent a persistent security vulnerability in AWS environments. During early prototyping or local development, developers frequently assign broad actions such as s3: or dynamodb: to expedite setup. Unfortunately, these wildcard configurations regularly find their way directly into production. Manually auditing and pruning permissions is both cumbersome and hazardous, as revoking required actions without visibility into application behavior can easily trigger unexpected outages.

AWS IAM Access Analyzer addresses this challenge by synthesizing refined, least-privilege IAM policies directly from observed CloudTrail activity logs. Rather than estimating necessary permissions, Access Analyzer audits actual API invocations made by your application and constructs a tailored JSON policy.

Generating Policies via the AWS CLI

IAM Access Analyzer evaluates CloudTrail management and data events across a designated timeframe to construct a policy that includes only the actions that were actively executed. The following Bash script automates policy generation and retrieval:

#!/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"

How the Automation Workflow Works

  • START_TIME and END_TIME: Establishes a 7-day observation window to capture routine traffic alongside periodic scheduled tasks, weekly batch processes, and cron jobs.
  • start-policy-generation: Directs Access Analyzer to analyze CloudTrail logs associated with the targeted ROLE_ARN. The service uses AnalyzerRole (which requires read access to CloudTrail logs stored in S3) to inspect historical requests.
  • Polling loop (while true ... sleep 10): Policy generation executes asynchronously within AWS. The script checks the job status every 10 seconds until processing concludes.
  • --include-resource-placeholders: Access Analyzer maps precise API calls (e.g., s3:GetObject), but may not consistently identify target resource ARNs. Setting this flag inserts placeholder variables like ${ResourceName} into the document, enabling manual substitution of specific bucket names or DynamoDB tables prior to deployment.
  • --query filter: Applies a JMESPath filter to extract the raw JSON policy document directly from the API output and write it locally for integration with Infrastructure as Code workflows like Terraform or CloudFormation.

Executing this evaluation cycle regularly across your application roles allows you to systematically remove wildcard permissions while ensuring service reliability and continuity.