> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Role Permissions That Can Modify Infrastructure

### More Info:

Identifies roles whose permissions let them create, change or delete infrastructure - compute, networking, storage and the accounts own guardrails. Every workload and user that can assume the role gets that write access, so an application flaw becomes an infrastructure change. Keep write permissions on dedicated roles, scope them to the resources the workload owns, and leave everything else read-only.

### Risk Level

High

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        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

        1. Sign in to the **AWS Management Console**.
        2. Go to **IAM**:
           * Search for “IAM” in the services search bar and open **IAM**.
        3. In the left menu, click **Access reports** → **Policy usage (AWS Managed Policies)** (if available in your region), or:
           * Click **Policies** and filter by:
             * `AdministratorAccess`
             * `PowerUserAccess`
             * Any custom policies that obviously grant high privileges (e.g., names containing `admin`, `fullaccess`, `*`).
        4. For each high‑privilege policy:
           1. Click the policy.
           2. Go to the **Policy usage** tab.
           3. 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:

        1. In **IAM → Roles**, click the role name.
        2. Check:
           * **Trust relationships** (who/what can assume the role).
           * **Permissions** tab → all attached policies.
        3. 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.

        1. In **IAM → Policies**, click **Create policy**.

        2. Choose **Visual editor** (easier through console).

        3. For each service the role truly needs:
           1. Click **Service** and select (for example):
              * `Amazon EC2`
              * `AWS Lambda`
              * `Amazon S3`
              * etc.
           2. 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 like `iam:CreateRole`, `iam:PutRolePolicy`, `iam:PassRole*` (unless truly required).
                * `cloudformation:*` if not really needed.
           3. Under **Resources**, avoid `All resources` where 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).

        4. Click **Next** → add **Tags** if needed → **Next: Review**.

        5. Give the policy a clear, descriptive name, e.g.:
           * `AppName-Role-LeastPrivilege-EC2Lambda-Policy`

        6. 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:

        1. In **IAM → Roles**, select the role.
        2. Go to the **Permissions** tab and click **Add permissions** → **Attach policies**.
        3. Search for and select the new custom policy(ies) you just created.
        4. Click **Next** → **Add permissions**.

        Then:

        5. Still on the role’s **Permissions** tab, identify over‑privileged policies such as:
           * `AdministratorAccess`
           * `PowerUserAccess`
           * Any policy with `*` on critical infra services.
        6. 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:

        1. **Limit IAM Changes**:
           * Avoid `iam:*`. Instead grant minimal:
             * `iam:PassRole` only for specific roles (narrow by ARN).
             * If needed, `iam:CreateRole`, `iam:AttachRolePolicy`, etc., but only for specific roles and with conditions.
        2. **Limit EC2 / VPC / RDS / CFN**:
           * Prefer granular actions: e.g., `ec2:DescribeInstances`, `ec2:RebootInstances` instead of `ec2:*`.
           * Restrict by **resource ARN** or **tags** when supported.
        3. **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.

        ***

        ## 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:

        1. Create a **permissions boundary policy** (IAM → Policies → Create policy) that:
           * Allows typical application actions.
           * Explicitly **denies** dangerous top‑level actions such as:
             * `iam:*` (or specifically `iam:CreateRole`, `iam:PutRolePolicy`, etc.).
             * `ec2:*` except for approved actions.
             * `cloudformation:*` if not required.
        2. Save the policy (e.g., `OrgAppRoles-PermissionsBoundary`).
        3. When a new role is created:
           * In **IAM → Roles → Create role**, on the **Permissions boundary** step select this policy.
        4. 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

        1. 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).
        2. 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.

        ***

        ## 8. Put Guardrails in Place (Optional but Recommended)

        1. **AWS Config Rules**:
           * Go to **AWS Config** in the console.
           * Set up rules such as:
             * `IAM_POLICY_NO_STATEMENTS_WITH_ADMIN_ACCESS`
             * `IAM_USER_NO_POLICIES_CHECK`
             * And other IAM‑related rules that flag over‑privileged policies.
        2. **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:

        1. Test role changes in **dev/stage** accounts first.
        2. Keep **versioned IAM policies** (JSON exported to Git or similar).
        3. 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.
      </Accordion>

      <Accordion title="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:

        ```bash theme={null}
        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

        ```bash theme={null}
        aws iam list-roles --query 'Roles[].RoleName' --output table
        ```

        ### 2.2 For each role, list its inline and attached policies

        Example for one role:

        ```bash theme={null}
        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:TerminateInstances`
        * `rds:*`, `lambda:*`, `elasticloadbalancing:*`, `eks:*`, `cloudformation:*`
        * `iam:Create*`, `iam:PutRolePolicy`, `iam:Attach*`, `iam:PassRole`
        * `autoscaling:*`, `elasticbeanstalk:*`, `apigateway:*`, `ecs:*`, `s3:CreateBucket`, `s3:PutBucketPolicy`, etc.

        ### 3.2 Inspect policies attached to a role

        #### a) Inline role policies

        ```bash theme={null}
        # 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

        ```bash theme={null}
        aws iam list-attached-role-policies \
          --role-name "$ROLE_NAME" \
          --query 'AttachedPolicies[].PolicyArn' \
          --output text
        ```

        For each ARN:

        ```bash theme={null}
        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:

        ```bash theme={null}
        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:

        1. **What is it supposed to do?** (Application role, CI/CD role, break-glass admin, etc.)
        2. 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.

        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

        ```bash theme={null}
        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

        ```bash theme={null}
        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:

        1. **Detach** from the role:
           ```bash theme={null}
           aws iam detach-role-policy \
             --role-name "$ROLE_NAME" \
             --policy-arn "arn:aws:iam::aws:policy/AdministratorAccess"
           ```

        2. **Create a new customer-managed least-privilege policy**:

           Prepare `least_priv_policy.json` with restricted actions/resources.

           ```bash theme={null}
           aws iam create-policy \
             --policy-name "LeastPrivilege-${ROLE_NAME}" \
             --policy-document file://least_priv_policy.json \
             --query 'Policy.Arn' --output text
           ```

        3. **Attach the new policy**:

           ```bash theme={null}
           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

        1. **Create a new version** with reduced permissions.

           Edit current document into `new_policy.json` (least privilege).

           ```bash theme={null}
           aws iam create-policy-version \
             --policy-arn "$POLICY_ARN" \
             --policy-document file://new_policy.json \
             --set-as-default
           ```

        2. Optionally, **delete old policy versions** later to stay under the 5-version limit.

           ```bash theme={null}
           aws iam list-policy-versions \
             --policy-arn "$POLICY_ARN" \
             --query 'Versions[?IsDefaultVersion==`false`].VersionId' \
             --output text
           ```

           Then:

           ```bash theme={null}
           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:
          ```json theme={null}
          "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

        ```bash theme={null}
        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:
          ```bash theme={null}
          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.
      </Accordion>

      <Accordion title="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:

        1. Identify “risky” IAM roles (those with permissions to change infrastructure such as EC2, IAM, CloudFormation, etc.).
        2. Review and reduce their permissions.
        3. Enforce a least‑privilege pattern.

        ***

        ## 1. Preparation

        **Install and configure AWS + Python tooling**

        ```bash theme={null}
        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:*` and `cloudtrail:*` (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

        ```python theme={null}
        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:

        ```bash theme={null}
        python discover_risky_roles.py
        ```

        Export results to a file if needed.

        ***

        ## 4. Analyze and Design Least‑Privilege Policies

        For each flagged role:

        1. Identify who/what uses the role (Lambda, EC2 instance profile, CI/CD, human, etc.).
        2. 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.
        3. Build a **minimal** policy:
           * Restrict to specific actions actually required (e.g., `ec2:DescribeInstances`, not `ec2:*`).
           * Restrict `Resource` to needed ARNs or patterns instead of `"*"` where possible.
           * Add conditions (e.g., limit by tag, region, or source IP if appropriate).

        ***

        ## 5. Remediate Policies via Python

        ### 5.1 Example: Replace `AdministratorAccess` with Custom Managed Policy

        1. Create a new least‑privilege managed policy.
        2. Attach it to the role.
        3. Detach the broad policy.

        ```python theme={null}
        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.

        ```python theme={null}
        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)

        1. **Service Control Policies (SCPs)** (in AWS Organizations):
           * Deny dangerous infra‑modifying actions account‑wide except from designated roles.
        2. **IAM permission boundaries**:
           * Ensure new roles cannot be given wider permissions than an approved “boundary” policy.
        3. **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

        1. Run discovery script → list risky roles.
        2. Prioritize by sensitivity (production, admin, CI/CD).
        3. 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.
        4. Re‑run the discovery script to confirm the role no longer has broad infra‑modifying permissions.
        5. 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.
      </Accordion>

      <Accordion title="Using Terraform">
        ```hcl theme={null}
        # 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_REGION` and `var.AWS_ACCOUNT_ID` with your variables or literals.
        * `INSTANCE_ID_1`, `INSTANCE_ID_2` with the specific instance IDs the workload may manage.
        * `SECURITY_GROUP_ID` with the specific security group ID.
        * `APP_OWNED_BUCKET_NAME` with the S3 bucket the workload owns.
        * `APP_INFRA_ROLE_NAME` with 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 existing `aws_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.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
