Role Infrastructure Modification Remediation
Triage and Remediation
- Remediation
Remediation
Using Console
Below is a practical, step‑by‑step way to find and fix IAM roles that have permissions to modify infrastructure in AWS, using only the AWS Console.
1. Identify High‑Risk Roles
- Sign in to the AWS Management Console.
- Go to IAM:
- Search for “IAM” in the services search bar and open IAM.
- In the left menu, click Access reports → Policy usage (AWS Managed Policies) (if available in your region), or:
- Click Policies and filter by:
AdministratorAccessPowerUserAccess- Any custom policies that obviously grant high privileges (e.g., names containing
admin,fullaccess,*).
- Click Policies and filter by:
- For each high‑privilege policy:
- Click the policy.
- Go to the Policy usage tab.
- Review which roles this policy is attached to.
Focus on roles that:
- Use AdministratorAccess, PowerUserAccess, or very broad
*permissions. - Have broad access to critical infra services:
ec2:*,iam:*,cloudformation:*,eks:*,rds:*,lambda:*,elasticloadbalancing:*, etc.
2. Understand What Each Role Really Needs
For each high‑risk role you found:
- In IAM → Roles, click the role name.
- Check:
- Trust relationships (who/what can assume the role).
- Permissions tab → all attached policies.
- Determine the role’s real purpose:
- Is it used by an application (e.g., EC2 instance profile, Lambda, ECS task)?
- Is it used by a human via AWS SSO or
sts:AssumeRole? - Is it used by a CI/CD system (e.g., GitHub Actions, Jenkins, CodePipeline)?
Ask (with your team if needed):
- Which specific services and actions does this role actually need to perform?
- What environments does it operate in (dev/test/prod)?
Document the minimal required permissions per role (services + actions + resource scopes).
3. Create a Least‑Privilege Custom Policy
You’ll now replace broad/infrastructure‑modifying permissions with a custom least‑privilege policy.
-
In IAM → Policies, click Create policy.
-
Choose Visual editor (easier through console).
-
For each service the role truly needs:
- Click Service and select (for example):
Amazon EC2AWS LambdaAmazon S3- etc.
- Under Actions, select only the exact Read/Write operations required, and avoid:
*(All EC2 actions, All IAM actions, etc.).- Any action that allows broad infra modification if not required, such as:
ec2:TerminateInstances,ec2:RunInstances,ec2:CreateVpc, etc.iam:*or powerful IAM actions likeiam:CreateRole,iam:PutRolePolicy,iam:PassRole*(unless truly required).cloudformation:*if not really needed.
- Under Resources, avoid
All resourceswhere possible:- Use ARNs to narrow to specific resources (e.g., a specific S3 bucket, specific Lambda functions, a particular VPC or EC2 instances via tags).
- Use Conditions to further restrict (e.g., only allow certain tags, regions, or actions from particular principals/IPs).
- Click Service and select (for example):
-
Click Next → add Tags if needed → Next: Review.
-
Give the policy a clear, descriptive name, e.g.:
AppName-Role-LeastPrivilege-EC2Lambda-Policy
-
Click Create policy.
Repeat as needed to create multiple smaller, purpose‑built policies instead of a single large “catch‑all” policy.
4. Apply the New Policy to the Role and Remove Over‑Privileged Ones
For each high‑risk role:
- In IAM → Roles, select the role.
- Go to the Permissions tab and click Add permissions → Attach policies.
- Search for and select the new custom policy(ies) you just created.
- Click Next → Add permissions.
Then:
- Still on the role’s Permissions tab, identify over‑privileged policies such as:
AdministratorAccessPowerUserAccess- Any policy with
*on critical infra services.
- For each such policy:
- Click the X or “Detach” button next to the policy.
- Confirm the detachment.
Change only one or a few roles at a time, and in lower environments first (dev/stage), to avoid breaking production.
5. Tighten IAM Permissions Specifically Related to Infrastructure
For roles that still must modify infrastructure (e.g., CI/CD roles, automation roles), scope them down:
- Limit IAM Changes:
- Avoid
iam:*. Instead grant minimal:iam:PassRoleonly for specific roles (narrow by ARN).- If needed,
iam:CreateRole,iam:AttachRolePolicy, etc., but only for specific roles and with conditions.
- Avoid
- Limit EC2 / VPC / RDS / CFN:
- Prefer granular actions: e.g.,
ec2:DescribeInstances,ec2:RebootInstancesinstead ofec2:*. - Restrict by resource ARN or tags when supported.
- Prefer granular actions: e.g.,
- Use Conditions in policies:
- Restrict to specific regions:
aws:RequestedRegion. - Restrict to specific VPC / tags when available.
- Restrict to service principals or specific users where relevant.
- Restrict to specific regions:
6. (Optional) Use IAM Permissions Boundaries for Shared/Delegated Roles
If you delegate creation of roles to different teams but want to ensure they cannot grant themselves infra‑wide powers:
- Create a permissions boundary policy (IAM → Policies → Create policy) that:
- Allows typical application actions.
- Explicitly denies dangerous top‑level actions such as:
iam:*(or specificallyiam:CreateRole,iam:PutRolePolicy, etc.).ec2:*except for approved actions.cloudformation:*if not required.
- Save the policy (e.g.,
OrgAppRoles-PermissionsBoundary). - When a new role is created:
- In IAM → Roles → Create role, on the Permissions boundary step select this policy.
- For existing roles (where needed):
- Go to the role → Permissions tab → Permissions boundary → Edit and attach the boundary.
This ensures even if a team attaches an overly broad policy, the boundary caps actual effective permissions.
7. Validate with IAM Access Analyzer and Access Advisor
- In IAM → Access Analyzer:
- Create an analyzer if not present.
- Review Findings for unusual or broad access (e.g., roles with external access or public access).
- In IAM → Roles, select a role and go to:
- Access Advisor tab (or Last accessed info):
- Inspect which services the role actually uses.
- If some services are never used, remove those permissions from the role’s policies.
- Access Advisor tab (or Last accessed info):
8. Put Guardrails in Place (Optional but Recommended)
- AWS Config Rules:
- Go to AWS Config in the console.
- Set up rules such as:
IAM_POLICY_NO_STATEMENTS_WITH_ADMIN_ACCESSIAM_USER_NO_POLICIES_CHECK- And other IAM‑related rules that flag over‑privileged policies.
- Security Hub (if used):
- Enable Foundational Security Best Practices standard.
- Review and remediate IAM‑related findings regularly.
9. Operate a Safe Change Process
For production roles that can modify infrastructure, add process protections:
- Test role changes in dev/stage accounts first.
- Keep versioned IAM policies (JSON exported to Git or similar).
- Implement change approvals for any role that:
- Can modify networks (VPC, Security Groups, Route Tables).
- Can create/modify IAM roles or policies.
- Can deploy infra via CloudFormation/Terraform/other IaC tools.
If you share an example of a specific role’s current policy (JSON or console screenshots), I can walk you through an exact “before → after” policy and the precise console clicks for that case.
Using CLI
Below is a concise, AWS-CLI–focused walkthrough to find and remediate IAM roles whose permissions can modify infrastructure (e.g., EC2, RDS, Lambda, CloudFormation, IAM, etc.).
1. Preparation
Configure AWS CLI with appropriate credentials and region:
aws configure
Ensure you’re using an admin or security/audit account to inspect and update IAM.
2. Identify IAM Roles and Attached Policies
2.1 List all roles
aws iam list-roles --query 'Roles[].RoleName' --output table
2.2 For each role, list its inline and attached policies
Example for one role:
ROLE_NAME="YourRoleName"
# Inline policies
aws iam list-role-policies \
--role-name "$ROLE_NAME" \
--query 'PolicyNames' --output table
# Attached managed policies
aws iam list-attached-role-policies \
--role-name "$ROLE_NAME" \
--query 'AttachedPolicies[].PolicyArn' --output table
3. Detect “Infrastructure-Modifying” Permissions
You’re looking for actions that can create/update/delete infrastructure, or grant such rights to others.
3.1 Common infrastructure-modifying actions
Examples (not exhaustive):
ec2:*,ec2:RunInstances,ec2:Create*,ec2:Modify*,ec2:TerminateInstancesrds:*,lambda:*,elasticloadbalancing:*,eks:*,cloudformation:*iam:Create*,iam:PutRolePolicy,iam:Attach*,iam:PassRoleautoscaling:*,elasticbeanstalk:*,apigateway:*,ecs:*,s3:CreateBucket,s3:PutBucketPolicy, etc.
3.2 Inspect policies attached to a role
a) Inline role policies
# For each inline policy name:
POLICY_NAME="InlinePolicyName"
aws iam get-role-policy \
--role-name "$ROLE_NAME" \
--policy-name "$POLICY_NAME" \
--query '{Role:RoleName,PolicyName:PolicyName,Document:PolicyDocument}' \
--output json
Review PolicyDocument.Statement[].Action for broad or dangerous actions (especially * or infra APIs above).
b) Attached managed policies
aws iam list-attached-role-policies \
--role-name "$ROLE_NAME" \
--query 'AttachedPolicies[].PolicyArn' \
--output text
For each ARN:
POLICY_ARN="arn:aws:iam::aws:policy/AdministratorAccess" # example
# Get default version
VERSION_ID=$(aws iam get-policy \
--policy-arn "$POLICY_ARN" \
--query 'Policy.DefaultVersionId' \
--output text)
aws iam get-policy-version \
--policy-arn "$POLICY_ARN" \
--version-id "$VERSION_ID" \
--query 'PolicyVersion.Document' \
--output json
Again, examine Statement[].Action for infra-modifying permissions.
You can script this to grep for suspicious services:
aws iam get-policy-version \
--policy-arn "$POLICY_ARN" \
--version-id "$VERSION_ID" \
--output json \
| jq '..|.Action? // empty' \
| grep -E 'ec2:|rds:|lambda:|eks:|cloudformation:|iam:'
4. Decide the Intended Scope (Least Privilege Design)
For each role:
- What is it supposed to do? (Application role, CI/CD role, break-glass admin, etc.)
- Map required actions:
- e.g., EC2 instance profile that only needs to read from S3:
s3:GetObject,s3:ListBucket. - CI/CD may need
cloudformation:*in specific stacks only.
- e.g., EC2 instance profile that only needs to read from S3:
Anything beyond that is “extra” and should be removed or tightly constrained.
5. Remediate Inline Policies (Using AWS CLI)
5.1 Export existing inline policy
aws iam get-role-policy \
--role-name "$ROLE_NAME" \
--policy-name "$POLICY_NAME" \
--query 'PolicyDocument' \
--output json > old_policy.json
5.2 Edit to least privilege
Manually edit old_policy.json:
- Remove wildcards like
"Action": "*". - Replace broad services like
"ec2:*"with specific actions (e.g.,"ec2:DescribeInstances"). - Add resource constraints where possible (e.g., specific ARNs instead of
"Resource": "*") - Add conditions where appropriate (e.g., IP, tags, MFA).
Save as new_policy.json.
5.3 Apply updated inline policy
aws iam put-role-policy \
--role-name "$ROLE_NAME" \
--policy-name "$POLICY_NAME" \
--policy-document file://new_policy.json
6. Remediate Managed Policies Attached to Roles
6.1 If AWS-managed (e.g., AdministratorAccess)
You cannot change AWS-managed policies; instead:
-
Detach from the role:
aws iam detach-role-policy \--role-name "$ROLE_NAME" \--policy-arn "arn:aws:iam::aws:policy/AdministratorAccess" -
Create a new customer-managed least-privilege policy:
Prepare
least_priv_policy.jsonwith restricted actions/resources.aws iam create-policy \--policy-name "LeastPrivilege-${ROLE_NAME}" \--policy-document file://least_priv_policy.json \--query 'Policy.Arn' --output text -
Attach the new policy:
NEW_POLICY_ARN="arn:aws:iam::<ACCOUNT_ID>:policy/LeastPrivilege-${ROLE_NAME}"aws iam attach-role-policy \--role-name "$ROLE_NAME" \--policy-arn "$NEW_POLICY_ARN"
6.2 If customer-managed
-
Create a new version with reduced permissions.
Edit current document into
new_policy.json(least privilege).aws iam create-policy-version \--policy-arn "$POLICY_ARN" \--policy-document file://new_policy.json \--set-as-default -
Optionally, delete old policy versions later to stay under the 5-version limit.
aws iam list-policy-versions \--policy-arn "$POLICY_ARN" \--query 'Versions[?IsDefaultVersion==`false`].VersionId' \--output textThen:
aws iam delete-policy-version \--policy-arn "$POLICY_ARN" \--version-id v2 # example
7. Lock Down IAM-Sensitive Actions
Special attention to:
iam:PassRole– restrict to specific role ARNs and services:"Condition": {"StringEquals": {"iam:PassedToService": ["ec2.amazonaws.com","lambda.amazonaws.com"]}}iam:AttachRolePolicy,iam:PutRolePolicy,iam:CreatePolicy,iam:CreateRole, etc.
Restrict to admin-only roles, or to specific ARNs.
Ensure only tightly controlled admin roles can modify IAM and broad infrastructure.
8. Validate Effective Permissions
8.1 Simulate policy for a role
aws iam simulate-principal-policy \
--policy-source-arn "arn:aws:iam::<ACCOUNT_ID>:role/$ROLE_NAME" \
--action-names ec2:RunInstances ec2:TerminateInstances iam:PassRole cloudformation:CreateStack \
--output table
Check which actions are “allowed.” Confirm only intended actions are permitted.
8.2 Optional: Enumerate all actions (coarse check)
Use a broad set (e.g., ec2:*, cloudformation:*) in simulation to see if any infra-modifying actions slip through.
9. Continuous Control
- Use IAM Access Analyzer to find overly broad permissions:
aws accessanalyzer list-analyzers
- Configure a CI check or script to:
- List roles.
- Parse attached policies.
- Flag roles with
Action: "*",resource: "*", or infra-modifying APIs.
If you share a sample policy document or a specific role name/policy, I can provide an exact CLI sequence and a concrete “before/after” JSON policy tailored to your use case.
Using Python
Below is a focused, step‑by‑step approach to remediating IAM roles that can modify infrastructure in AWS, using Python (boto3). I’ll assume you want to:
- Identify “risky” IAM roles (those with permissions to change infrastructure such as EC2, IAM, CloudFormation, etc.).
- Review and reduce their permissions.
- Enforce a least‑privilege pattern.
1. Preparation
Install and configure AWS + Python tooling
pip install boto3
aws configure
Make sure the credentials you use have permission to call iam:ListRoles, iam:ListAttachedRolePolicies, iam:ListRolePolicies, iam:GetPolicy, iam:GetPolicyVersion, iam:GetRolePolicy.
2. Define What “Can Modify Infrastructure” Means
Typical infra‑modifying services (example, adjust as needed):
ec2:*iam:*cloudformation:*lambda:*rds:*elasticloadbalancing:*autoscaling:*eks:*route53:*organizations:*s3:*(if modifying buckets that hold infra state, e.g., Terraform state)ssm:*(if used to push changes)logs:*andcloudtrail:*(if you want to protect logging infra)
High‑risk patterns:
*:*service:*(e.g.,ec2:*)- Specific mutating actions:
RunInstances,Create*,Update*,Delete*,Put*,Attach*,Detach*,Modify*,Set*, etc.
Create a Python list/regex of these actions or service prefixes.
3. Python Script: Discover Risky IAM Roles
This script:
- Lists all roles
- Retrieves attached managed policies
- Retrieves inline policies
- Parses policy documents
- Flags roles that can modify infrastructure
import boto3
import json
import re
iam = boto3.client('iam')
# Services/actions considered infra-modifying (customize this list)
INFRA_SERVICE_PREFIXES = [
'ec2', 'iam', 'cloudformation', 'lambda', 'rds', 'elasticloadbalancing',
'autoscaling', 'eks', 'route53', 'organizations', 's3', 'ssm'
]
# Regex to catch mutating actions, e.g., Create*, Delete*, Modify*, etc.
MUTATING_ACTION_REGEX = re.compile(r'^(Create|Delete|Update|Put|Modify|Attach|Detach|Set|Revoke|Terminate).*')
def is_infra_modifying_action(action):
"""
Return True if the action is infra-modifying according to our rules.
"""
if action == '*': # Full admin
return True
# Normalized like 'ec2:RunInstances'
if ':' not in action:
return False
service, op = action.split(':', 1)
service = service.lower()
if service in INFRA_SERVICE_PREFIXES:
if op == '*' or MUTATING_ACTION_REGEX.match(op):
return True
return False
def expand_statement_actions(statement):
"""
Normalize and expand the 'Action' or 'NotAction' fields in a policy statement.
For simplicity, we only handle 'Action'.
"""
actions = statement.get('Action', [])
if isinstance(actions, str):
actions = [actions]
return actions
def role_has_infra_modify_perms(policy_doc):
"""
Given a policy JSON dict, determine if it grants infra-modifying permissions.
"""
if policy_doc.get('Version') is None:
return False
for stmt in policy_doc.get('Statement', []):
if isinstance(stmt, dict):
effect = stmt.get('Effect', 'Allow')
if effect != 'Allow':
continue
actions = expand_statement_actions(stmt)
for action in actions:
if is_infra_modifying_action(action):
return True
return False
def get_managed_policies_for_role(role_name):
attached = iam.list_attached_role_policies(RoleName=role_name)['AttachedPolicies']
return attached
def get_policy_doc(arn):
policy = iam.get_policy(PolicyArn=arn)['Policy']
version_id = policy['DefaultVersionId']
version = iam.get_policy_version(PolicyArn=arn, VersionId=version_id)
return version['PolicyVersion']['Document']
def get_inline_policies_for_role(role_name):
inline_names = iam.list_role_policies(RoleName=role_name)['PolicyNames']
policies = []
for name in inline_names:
pol = iam.get_role_policy(RoleName=role_name, PolicyName=name)
policies.append(pol['PolicyDocument'])
return policies
def discover_risky_roles():
paginator = iam.get_paginator('list_roles')
risky_roles = []
for page in paginator.paginate():
for role in page['Roles']:
role_name = role['RoleName']
is_risky = False
details = []
# Managed policies
for attached in get_managed_policies_for_role(role_name):
pol_arn = attached['PolicyArn']
pol_name = attached['PolicyName']
doc = get_policy_doc(pol_arn)
if role_has_infra_modify_perms(doc):
is_risky = True
details.append(f"ManagedPolicy: {pol_name} ({pol_arn})")
# Inline policies
for inline_doc in get_inline_policies_for_role(role_name):
if role_has_infra_modify_perms(inline_doc):
is_risky = True
details.append("InlinePolicy")
if is_risky:
risky_roles.append({
'RoleName': role_name,
'Arn': role['Arn'],
'Reason': details
})
return risky_roles
if __name__ == '__main__':
risky = discover_risky_roles()
print("Roles with infra-modifying permissions:")
for r in risky:
print(f"- {r['RoleName']} ({r['Arn']}) -> {', '.join(r['Reason'])}")
Run it:
python discover_risky_roles.py
Export results to a file if needed.
4. Analyze and Design Least‑Privilege Policies
For each flagged role:
- Identify who/what uses the role (Lambda, EC2 instance profile, CI/CD, human, etc.).
- Get CloudTrail logs or service usage reports to know exactly which actions/resources are actually used.
- Use AWS Console: CloudTrail → Event history (filter by role ARN).
- Or Athena queries over CloudTrail logs to list actions used.
- Build a minimal policy:
- Restrict to specific actions actually required (e.g.,
ec2:DescribeInstances, notec2:*). - Restrict
Resourceto needed ARNs or patterns instead of"*"where possible. - Add conditions (e.g., limit by tag, region, or source IP if appropriate).
- Restrict to specific actions actually required (e.g.,
5. Remediate Policies via Python
5.1 Example: Replace AdministratorAccess with Custom Managed Policy
- Create a new least‑privilege managed policy.
- Attach it to the role.
- Detach the broad policy.
import boto3
import json
iam = boto3.client('iam')
def create_or_update_managed_policy(policy_name, policy_doc):
try:
iam.get_policy(PolicyArn=f'arn:aws:iam::YOUR_ACCOUNT_ID:policy/{policy_name}')
# Policy exists: create a new version
iam.create_policy_version(
PolicyArn=f'arn:aws:iam::YOUR_ACCOUNT_ID:policy/{policy_name}',
PolicyDocument=json.dumps(policy_doc),
SetAsDefault=True
)
print(f"Updated managed policy {policy_name}")
except iam.exceptions.NoSuchEntityException:
iam.create_policy(
PolicyName=policy_name,
PolicyDocument=json.dumps(policy_doc)
)
print(f"Created managed policy {policy_name}")
def switch_role_policies(role_name, old_policy_arn, new_policy_arn):
# Attach new least-privilege policy
iam.attach_role_policy(RoleName=role_name, PolicyArn=new_policy_arn)
# Detach old over-privileged policy
iam.detach_role_policy(RoleName=role_name, PolicyArn=old_policy_arn)
print(f"Switched {role_name} from {old_policy_arn} to {new_policy_arn}")
# Example: build a tailored policy (replace with your least-privilege definition)
least_priv_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSpecificEC2Reads",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeTags"
],
"Resource": "*"
}
]
}
if __name__ == '__main__':
create_or_update_managed_policy('LeastPrivilegeMyApp', least_priv_policy)
# Replace AdministratorAccess (example ARN) on MyAppRole
old_admin_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
new_lp_arn = "arn:aws:iam::YOUR_ACCOUNT_ID:policy/LeastPrivilegeMyApp"
switch_role_policies("MyAppRole", old_admin_arn, new_lp_arn)
5.2 Example: Tighten Inline Policy
If a role has an inline policy that’s too broad, replace it with a stricter one.
def update_inline_policy(role_name, policy_name, new_doc):
iam.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=json.dumps(new_doc)
)
print(f"Updated inline policy {policy_name} on role {role_name}")
You’d construct new_doc (least‑privilege) based on CloudTrail usage.
6. Implement Guardrails (Optional but Recommended)
- Service Control Policies (SCPs) (in AWS Organizations):
- Deny dangerous infra‑modifying actions account‑wide except from designated roles.
- IAM permission boundaries:
- Ensure new roles cannot be given wider permissions than an approved “boundary” policy.
- Automated checks:
- Integrate the detection script into CI/CD.
- Fail builds or send alerts if a role is created/updated with infra‑modifying permissions outside a whitelist.
7. Practical Workflow Summary
- Run discovery script → list risky roles.
- Prioritize by sensitivity (production, admin, CI/CD).
- For each role:
- Review actual usage via CloudTrail.
- Design a minimal policy (actions + resources + conditions).
- Update the role via Python: create managed policy / update inline policy → detach/remove broad policies.
- Re‑run the discovery script to confirm the role no longer has broad infra‑modifying permissions.
- Add guardrails (SCPs, permission boundaries) to prevent regressions.
If you share an example role ARN/policy, I can help you write the exact Python code and least‑privilege replacement for that specific case.
Using Terraform
# IAM policy with restricted, least-privilege infrastructure access.
# Replace the existing aws_iam_policy that currently grants broad write
# (e.g. ec2:*, iam:*, "*") with a scoped version like this.
data "aws_iam_policy_document" "APP_INFRA_ROLE_POLICY" {
# Read-only access to describe infrastructure across the account
statement {
sid = "ReadOnlyInfra"
effect = "Allow"
actions = [
"ec2:Describe*",
"elasticloadbalancing:Describe*",
"autoscaling:Describe*",
"rds:Describe*",
"eks:Describe*",
"eks:List*",
"iam:Get*",
"iam:List*",
"logs:Describe*",
"logs:Get*",
"logs:List*",
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*",
"s3:GetBucketLocation",
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:GetBucketPolicy",
]
resources = ["*"]
}
# WRITE access only to the specific infrastructure the workload owns.
# SCOPE THESE ARNs to just what this role needs to manage.
# Example: manage EC2 instances only in a specific autoscaling group/tagged set
statement {
sid = "ScopedEC2Write"
effect = "Allow"
actions = [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:RebootInstances",
"ec2:DescribeInstances",
"ec2:CreateTags",
"ec2:DeleteTags",
]
# Replace with the actual instance ARNs or a constrained pattern
resources = [
"arn:aws:ec2:${var.AWS_REGION}:${var.AWS_ACCOUNT_ID}:instance/INSTANCE_ID_1",
"arn:aws:ec2:${var.AWS_REGION}:${var.AWS_ACCOUNT_ID}:instance/INSTANCE_ID_2",
]
}
# Example: manage only one specific security group
statement {
sid = "ScopedSecurityGroupWrite"
effect = "Allow"
actions = [
"ec2:AuthorizeSecurityGroupIngress",
"ec2:RevokeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:RevokeSecurityGroupEgress",
"ec2:DescribeSecurityGroups",
]
resources = [
"arn:aws:ec2:${var.AWS_REGION}:${var.AWS_ACCOUNT_ID}:security-group/SECURITY_GROUP_ID",
]
}
# Example: manage only one specific S3 bucket the workload owns
statement {
sid = "ScopedS3Write"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
]
resources = [
"arn:aws:s3:::APP_OWNED_BUCKET_NAME",
"arn:aws:s3:::APP_OWNED_BUCKET_NAME/*",
]
}
# Explicitly deny high-risk guardrail changes for this role
# (helpful if this policy is ever combined with broader ones via inheritance)
statement {
sid = "DenyGuardrailChanges"
effect = "Deny"
actions = [
"iam:CreatePolicy",
"iam:DeletePolicy",
"iam:CreateRole",
"iam:DeleteRole",
"iam:PutRolePolicy",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:PutUserPolicy",
"iam:AttachUserPolicy",
"iam:DetachUserPolicy",
"iam:PutGroupPolicy",
"iam:AttachGroupPolicy",
"iam:DetachGroupPolicy",
"organizations:*",
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"config:DeleteConfigurationRecorder",
"config:StopConfigurationRecorder",
"config:DeleteConfigRule",
"shield:DeleteProtection",
]
resources = ["*"]
}
}
resource "aws_iam_policy" "APP_INFRA_ROLE_POLICY" {
name = "APP_INFRA_ROLE_POLICY"
description = "Least-privilege, scoped infra access for APP_INFRA_ROLE; no broad infra-modify permissions."
policy = data.aws_iam_policy_document.APP_INFRA_ROLE_POLICY.json
}
# Attach this policy only to the dedicated application role, not to generic
# human/admin roles. Replace APP_INFRA_ROLE_NAME with the IAM role name.
resource "aws_iam_role_policy_attachment" "APP_INFRA_ROLE_ATTACHMENT" {
role = "APP_INFRA_ROLE_NAME" # substitute with the IAM role that should have this access
policy_arn = aws_iam_policy.APP_INFRA_ROLE_POLICY.arn
}
Substitute:
var.AWS_REGIONandvar.AWS_ACCOUNT_IDwith your variables or literals.INSTANCE_ID_1,INSTANCE_ID_2with the specific instance IDs the workload may manage.SECURITY_GROUP_IDwith the specific security group ID.APP_OWNED_BUCKET_NAMEwith the S3 bucket the workload owns.APP_INFRA_ROLE_NAMEwith the IAM role that should have this scoped infra-write access.
This change updates the IAM policy in place (no resource replacement of the role itself), but it may remove permissions workloads currently rely on; test in a non-production environment first.
To verify, terraform plan should show:
~(update in-place) for the existingaws_iam_policy(or a new+if you are introducing this policy for the first time).- No changes that recreate the IAM role, only attachment changes if you are switching from a previous broad policy to this scoped one.