> ## 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 privilege escalation remediation

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        Below is a practical, console-focused process to find and fix IAM role permissions that allow privilege escalation in AWS.

        ***

        ## 1. Understand What You’re Looking For

        Privilege escalation usually happens when a principal (user/role) can:

        * Attach/Update IAM policies to themselves or to roles they can assume:
          * `iam:AttachUserPolicy`, `iam:AttachRolePolicy`, `iam:PutUserPolicy`, `iam:PutRolePolicy`, `iam:CreatePolicyVersion`, `iam:SetDefaultPolicyVersion`, etc.
        * Assume or pass roles with more privileges:
          * `sts:AssumeRole`
          * `iam:PassRole` (combined with other actions)
        * Create or modify access keys / login profiles:
          * `iam:CreateAccessKey`, `iam:UpdateAccessKey`, `iam:CreateLoginProfile`, `iam:UpdateLoginProfile`
        * Create/Update Lambda, EC2, or other services with privileged roles:
          * `lambda:UpdateFunctionCode`, `lambda:CreateFunction`, `ec2:RunInstances` with a powerful instance profile, etc.

        Your goal: Remove or tightly restrict these permissions to least privilege.

        ***

        ## 2. Inventory IAM Roles and Attached Policies

        1. Sign in to the AWS Management Console with an admin account.
        2. Go to **IAM**:
           * Top right search bar → type “IAM” → **IAM**.
        3. In the left navigation, check:
           * **Roles**
           * **Users**
           * **Policies**

        You’ll review permissions on:

        * Managed policies (AWS-managed & Customer-managed).
        * Inline policies on users/roles/groups.

        ***

        ## 3. Identify High-Risk Policies Using IAM Access Analyzer

        1. In IAM console, left menu → **Access Analyzer** → **Policy analyzer**.
        2. Use **Policy analyzer** to search for policies containing sensitive actions:
           * In **Actions** filter, enter:
             * `iam:*`, `sts:*`, `lambda:*`, `ec2:RunInstances`, etc.
        3. For each policy result, open it in a new tab and inspect:
           * Look for statements with `Effect: Allow` and:
             * `Action: "iam:*"` or broad wildcards like `"*"` on **Action**.
             * `Resource: "*"`.
        4. Prioritize:
           * Policies with `Action` = `"*"` and `Resource` = `"*"`.
           * Policies that allow specific escalation actions (list in section 1).

        ***

        ## 4. Analyze a Specific Role for Escalation Path

        To deep dive into one role:

        1. IAM console → **Roles**.
        2. Click a **role name** to inspect it.
        3. Tabs to check:
           * **Permissions**:
             * Look at each **Attached policy** and any **Inline policies**.
             * Click policy name → **JSON** tab.
           * **Trust relationships**:
             * Check which principals can assume this role (`sts:AssumeRole`).

        For each attached/inline policy, look for:

        * Problematic *Actions*:

          Common privilege-escalation enablers:

          * `iam:AttachUserPolicy`
          * `iam:AttachGroupPolicy`
          * `iam:AttachRolePolicy`
          * `iam:PutUserPolicy`
          * `iam:PutGroupPolicy`
          * `iam:PutRolePolicy`
          * `iam:CreatePolicyVersion`
          * `iam:SetDefaultPolicyVersion`
          * `iam:PassRole`
          * `sts:AssumeRole`
          * `iam:CreateAccessKey`
          * `iam:UpdateAccessKey`
          * `iam:CreateLoginProfile`
          * `iam:UpdateLoginProfile`

        * Broad *Resources*:
          * `Resource: "*"` on the above actions.
          * Or resources that cover admin roles/policies.

        If a role a user has can assume another highly privileged role (via `sts:AssumeRole`), that is a privilege escalation path.

        ***

        ## 5. Remediate Policies (Least-Privilege Hardening)

        ### 5.1 Remove or Restrict Dangerous IAM Actions

        For each risky policy:

        1. IAM → **Policies** → search for the policy name.

        2. Open policy → **Permissions** tab → **Edit**.

        3. In **JSON** or **Visual editor**, do the following:

           a) Remove unneeded IAM-write actions completely\
           For example, remove:

           * `iam:Attach*`
           * `iam:Put*`
           * `iam:CreatePolicyVersion`
           * `iam:SetDefaultPolicyVersion`
           * `iam:CreateAccessKey`
           * `iam:UpdateAccessKey`
           * `iam:CreateLoginProfile`
           * `iam:UpdateLoginProfile`

           b) Where `iam:PassRole` is required:

           * Restrict **Resource** to specific safe roles, not `"*"`.
           * Add conditions like:
             ```json theme={null}
             "Condition": {
               "StringEquals": {
                 "iam:PassedToService": [
                   "ecs.amazonaws.com",
                   "lambda.amazonaws.com"
                 ]
               }
             }
             ```

           c) Where `sts:AssumeRole` is required:

           * Restrict to only the specific role ARNs needed.
           * Do not allow `"*"` for resources.

        4. Click **Next**, review, then **Save changes**.

        If an AWS-managed policy is risky (you can’t edit it):

        * Detach it from users/roles and replace with a custom, scoped-down customer-managed policy:
          1. **Policies** → **Create policy**.
          2. Define least-privilege permissions.
          3. Create policy, then **Attach** it to the target role/user.
          4. Detach the AWS-managed policy from the entity.

        ### 5.2 Tighten Trust Policies (Who Can Assume the Role)

        1. IAM → **Roles** → choose role → **Trust relationships** tab.

        2. Click **Edit trust policy**.

        3. Check for:
           * `"Principal": "*"` or overly broad accounts.
           * Conditions missing on `sts:AssumeRole`.

        4. Adjust to only allow required principals and, ideally, require conditions. Example (only one account and specific role):

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": { "AWS": "arn:aws:iam::123456789012:role/AppRole" },
              "Action": "sts:AssumeRole"
            }
          ]
        }
        ```

        Avoid letting low-privilege roles, wildcards, or external accounts assume highly privileged roles.

        ***

        ## 6. Use the IAM Policy Simulator to Validate

        1. Go to **IAM → Policy Simulator**.
        2. Choose **User** or **Role** you want to test.
        3. Select services like **IAM**, **STS**, **Lambda**, **EC2**.
        4. Test specific actions:
           * `iam:PassRole`, `iam:AttachRolePolicy`, `sts:AssumeRole`, `iam:CreateAccessKey`, etc.
        5. Confirm:
           * Only the minimal, intended actions are allowed.
           * Escalation-enabling combinations are not possible.

        ***

        ## 7. Add Guardrails: SCPs and Permissions Boundaries (If Using Organizations)

        If you use AWS Organizations:

        1. **Service Control Policies (SCPs)**:

           * AWS Organizations console → **Policies → Service control policies**.
           * Create SCPs that *deny* dangerous actions for all non-admin accounts, e.g.:

           ```json theme={null}
           {
             "Version": "2012-10-17",
             "Statement": [
               {
                 "Effect": "Deny",
                 "Action": [
                   "iam:CreatePolicyVersion",
                   "iam:SetDefaultPolicyVersion",
                   "iam:AttachRolePolicy",
                   "iam:PassRole"
                 ],
                 "Resource": "*"
               }
             ]
           }
           ```

           * Attach SCP to appropriate OUs/accounts.
           * Exclude a tightly-controlled admin account if necessary.

        2. **Permissions boundaries** (optional, more advanced):
           * Create a boundary policy that *excludes* privilege-escalation actions.
           * Attach as permission boundary to roles developers can create.

        ***

        ## 8. Continuous Monitoring

        1. IAM console → **Access Analyzer → Analyzer**:
           * Enable analyzers to detect broad or external access.
        2. **IAM → Credential report**:
           * Periodically review user access keys, console access.
        3. Enable **CloudTrail** and (optionally) **CloudTrail Lake / GuardDuty**:
           * Monitor for suspicious IAM activity (e.g., new `CreatePolicyVersion`, `SetDefaultPolicyVersion`, `AttachRolePolicy`).

        ***

        If you share a specific risky role/policy JSON, I can give exact edits line-by-line for remediation.
      </Accordion>

      <Accordion title="Using CLI">
        Below is a practical, CLI‑focused process to identify and fix IAM role permissions that allow privilege escalation in AWS.

        ***

        ## 1. Know the risky IAM permissions

        These actions are commonly used for privilege escalation when overly broad:

        * `iam:PassRole`
        * `iam:PutRolePolicy`
        * `iam:AttachRolePolicy`
        * `iam:PutUserPolicy`
        * `iam:CreatePolicyVersion`
        * `iam:SetDefaultPolicyVersion`
        * `iam:UpdateAssumeRolePolicy`
        * `iam:AddUserToGroup`, `iam:AttachGroupPolicy`
        * `sts:AssumeRole`
        * `sts:AssumeRoleWithWebIdentity`

        Your goal: **find any role that can call these actions in a dangerous way** (e.g., unscoped to specific resources, or to Admin policies/roles).

        ***

        ## 2. Enumerate IAM roles and their policies

        ```bash theme={null}
        # List all roles
        aws iam list-roles --query 'Roles[*].RoleName' --output text
        ```

        For each role (or for ones you suspect):

        ```bash theme={null}
        ROLE_NAME=YourRoleName

        # Inline policies on the role
        aws iam list-role-policies --role-name "$ROLE_NAME"

        # Get contents of each inline policy
        aws iam get-role-policy \
          --role-name "$ROLE_NAME" \
          --policy-name YourInlinePolicyName

        # Attached managed policies
        aws iam list-attached-role-policies --role-name "$ROLE_NAME"

        # For each attached policy, get its default policy document
        POLICY_ARN=arn:aws:iam::aws:policy/YourPolicy
        DEFAULT_VERSION=$(aws iam get-policy \
          --policy-arn "$POLICY_ARN" \
          --query 'Policy.DefaultVersionId' \
          --output text)

        aws iam get-policy-version \
          --policy-arn "$POLICY_ARN" \
          --version-id "$DEFAULT_VERSION"
        ```

        Look in the policy JSON for `Action` containing the risky IAM actions above.

        ***

        ## 3. Identify privilege‑escalation patterns

        Flags to look for in the policy JSON:

        1. **Wildcard actions**:
           * `"Action": "iam:*"` or `"*"` on IAM / STS
        2. **Unrestricted `PassRole`**:
           * `"Action": "iam:PassRole"` with `"Resource": "*"`
        3. **Policy modification on high‑privilege policies**:
           * `iam:AttachRolePolicy`, `iam:PutRolePolicy`, `iam:CreatePolicyVersion`, `iam:SetDefaultPolicyVersion` with `"Resource": "*"`
        4. **Trust policies that are too broad**:
           * `sts:AssumeRole` allowed from `Principal: "*"`, or from untrusted accounts/services.

        To view a role’s trust policy:

        ```bash theme={null}
        aws iam get-role --role-name "$ROLE_NAME" \
          --query 'Role.AssumeRolePolicyDocument' \
          --output json
        ```

        ***

        ## 4. Remove or scope dangerous permissions (inline policies)

        ### 4.1. Download and edit the inline policy locally

        ```bash theme={null}
        ROLE_NAME=YourRoleName
        POLICY_NAME=YourInlinePolicyName

        aws iam get-role-policy \
          --role-name "$ROLE_NAME" \
          --policy-name "$POLICY_NAME" \
          --query 'PolicyDocument' \
          --output json > policy.json
        ```

        Edit `policy.json`:

        * Remove unneeded risky actions (e.g., `iam:*`, `iam:PassRole`, etc.).
        * Or **narrow** them:
          * Restrict `Resource` to only specific ARNs (e.g., just one role that truly needs to be passed).
          * Add `Condition` (e.g., limit `iam:PassRole` to certain services).

        Example: scope `iam:PassRole` to a single role:

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "AllowPassSpecificRoleToEC2",
              "Effect": "Allow",
              "Action": "iam:PassRole",
              "Resource": "arn:aws:iam::123456789012:role/EC2LimitedRole",
              "Condition": {
                "StringEquals": {
                  "iam:PassedToService": "ec2.amazonaws.com"
                }
              }
            }
          ]
        }
        ```

        Re‑apply the edited inline policy:

        ```bash theme={null}
        aws iam put-role-policy \
          --role-name "$ROLE_NAME" \
          --policy-name "$POLICY_NAME" \
          --policy-document file://policy.json
        ```

        ***

        ## 5. Replace or detach risky managed policies

        If a role has broad AWS‑managed or customer‑managed policies (e.g., `AdministratorAccess`, custom `*:*` policies):

        ```bash theme={null}
        ROLE_NAME=YourRoleName
        POLICY_ARN=arn:aws:iam::aws:policy/AdministratorAccess

        # Detach the overly permissive policy
        aws iam detach-role-policy \
          --role-name "$ROLE_NAME" \
          --policy-arn "$POLICY_ARN"
        ```

        Create a **least‑privilege** customer‑managed policy:

        ```bash theme={null}
        cat > least-priv-role-policy.json << 'EOF'
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "AppNeedsOnlyTheseActions",
              "Effect": "Allow",
              "Action": [
                "s3:GetObject",
                "s3:PutObject"
              ],
              "Resource": "arn:aws:s3:::my-bucket/*"
            }
          ]
        }
        EOF

        aws iam create-policy \
          --policy-name MyLeastPrivPolicy \
          --policy-document file://least-priv-role-policy.json

        NEW_POLICY_ARN=$(aws iam list-policies \
          --scope Local \
          --query "Policies[?PolicyName=='MyLeastPrivPolicy'].Arn" \
          --output text)

        aws iam attach-role-policy \
          --role-name "$ROLE_NAME" \
          --policy-arn "$NEW_POLICY_ARN"
        ```

        ***

        ## 6. Lock down trust policies (AssumeRole)

        For any role that can lead to higher privilege (e.g., admin roles):

        1. Restrict who can assume it.
        2. Avoid `Principal: "*"`.
        3. Limit to specific AWS accounts, roles, or services.

        Get and edit trust policy:

        ```bash theme={null}
        ROLE_NAME=PrivilegedRole

        aws iam get-role --role-name "$ROLE_NAME" \
          --query 'Role.AssumeRolePolicyDocument' \
          --output json > trust.json
        ```

        Example safer trust policy (from a specific account/role only):

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "AWS": "arn:aws:iam::111122223333:role/ApprovedCallerRole"
              },
              "Action": "sts:AssumeRole",
              "Condition": {
                "Bool": {
                  "aws:MultiFactorAuthPresent": "true"
                }
              }
            }
          ]
        }
        ```

        Apply:

        ```bash theme={null}
        aws iam update-assume-role-policy \
          --role-name "$ROLE_NAME" \
          --policy-document file://trust.json
        ```

        ***

        ## 7. Fix dangerous policy versioning rights

        If a role can call `iam:CreatePolicyVersion` or `iam:SetDefaultPolicyVersion` on sensitive policies, an attacker can swap in an admin version.

        For customer‑managed policies:

        1. List who can modify them.
        2. Remove those actions, or scope them with conditions (e.g., only to non‑privileged policies, or only a CI/CD role).

        To reduce rights, edit the responsible role’s policy as in step 4 and remove:

        * `iam:CreatePolicyVersion`
        * `iam:SetDefaultPolicyVersion`
        * `iam:DeletePolicyVersion`

        Then re‑apply with `put-role-policy`.

        ***

        ## 8. Validate with the Policy Simulator

        Before and after changes, you can check what a role can do:

        ```bash theme={null}
        ROLE_ARN=arn:aws:iam::123456789012:role/YourRole

        aws iam simulate-principal-policy \
          --policy-source-arn "$ROLE_ARN" \
          --action-names iam:PassRole iam:CreatePolicyVersion iam:AttachRolePolicy sts:AssumeRole \
          --output json
        ```

        Verify that high‑risk actions are `explicitDeny` or not allowed.

        ***

        ## 9. Optional: enforce org‑wide via SCPs (if using AWS Organizations)

        At the org/root or OU level, create SCPs that deny dangerous privilege‑escalation patterns except for tightly controlled admin roles:

        Example SCP snippet:

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "DenyPassRoleToAnyRoleExceptAdminControllers",
              "Effect": "Deny",
              "Action": "iam:PassRole",
              "Resource": "*",
              "Condition": {
                "StringNotLike": {
                  "aws:PrincipalArn": [
                    "arn:aws:iam::*:role/OrgAdminRole"
                  ]
                }
              }
            }
          ]
        }
        ```

        Apply with:

        ```bash theme={null}
        aws organizations create-policy \
          --content file://scp.json \
          --name RestrictPassRole \
          --type SERVICE_CONTROL_POLICY
        # then attach to OU or root
        ```

        ***

        If you want, you can paste one specific role’s current policy JSON, and I can show you exactly how to rewrite it to remove the privilege‑escalation paths and give you the exact `aws cli` commands to apply the fix.
      </Accordion>

      <Accordion title="Using Python">
        Below is a practical, step‑by‑step remediation approach for AWS IAM privilege‑escalation risks, with Python (boto3) examples.

        ***

        ## 1. Understand the Common IAM Privilege Escalation Paths

        Typical dangerous permissions (especially if a user/principal can call them on themselves or roles they assume):

        * `iam:CreatePolicyVersion` (with `SetAsDefault=true`)
        * `iam:PutRolePolicy`, `iam:PutUserPolicy`, `iam:AttachRolePolicy`, `iam:AttachUserPolicy`, `iam:AttachGroupPolicy`
        * `iam:PassRole` (especially combined with EC2, Lambda, Step Functions, etc.)
        * `sts:AssumeRole`
        * Permissions on Lambda, EC2, Glue, Step Functions, etc., that allow attaching or modifying roles/policies:
          * `lambda:UpdateFunctionConfiguration` (to change execution role)
          * `ec2:RunInstances` with instance profile you can control
          * `glue:CreateDevEndpoint` with role you can pass
        * Wildcards like `"Action": "*"`, `"Resource": "*"` (especially in combination).

        Remediation = remove or restrict these where they are not explicitly needed, and add guardrails (permission boundaries, SCPs, condition keys).

        ***

        ## 2. Enumerate IAM Roles and Policies (Python/boto3)

        ```python theme={null}
        import boto3
        import json

        iam = boto3.client('iam')

        def list_all_roles():
            roles = []
            paginator = iam.get_paginator('list_roles')
            for page in paginator.paginate():
                roles.extend(page['Roles'])
            return roles

        def list_attached_policies_for_role(role_name):
            attached = []
            paginator = iam.get_paginator('list_attached_role_policies')
            for page in paginator.paginate(RoleName=role_name):
                attached.extend(page['AttachedPolicies'])
            return attached

        def list_inline_policies_for_role(role_name):
            policies = []
            paginator = iam.get_paginator('list_role_policies')
            for page in paginator.paginate(RoleName=role_name):
                policies.extend(page['PolicyNames'])
            return policies
        ```

        ***

        ## 3. Identify High‑Risk Permissions in Policies

        Define a set of “risky” actions and scan all attached + inline policies.

        ```python theme={null}
        RISKY_ACTIONS = {
            "iam:CreatePolicyVersion",
            "iam:PutRolePolicy",
            "iam:PutUserPolicy",
            "iam:AttachRolePolicy",
            "iam:AttachUserPolicy",
            "iam:AttachGroupPolicy",
            "iam:PassRole",
            "iam:PutRolePermissionsBoundary",
            "iam:PutUserPermissionsBoundary",
            "sts:AssumeRole",
            "*",  # catch-all, will treat carefully
        }

        def get_policy_document_arn(policy_arn, version_id=None):
            if not version_id:
                policy = iam.get_policy(PolicyArn=policy_arn)
                version_id = policy['Policy']['DefaultVersionId']
            version = iam.get_policy_version(PolicyArn=policy_arn, VersionId=version_id)
            return version['PolicyVersion']['Document']

        def get_inline_policy_document(role_name, policy_name):
            resp = iam.get_role_policy(RoleName=role_name, PolicyName=policy_name)
            return resp['PolicyDocument']

        def statement_contains_risky_actions(statement):
            actions = statement.get("Action", [])
            if isinstance(actions, str):
                actions = [actions]
            actions = [a.lower() for a in actions]
            risky = []
            for a in actions:
                if a == "*" or a in {x.lower() for x in RISKY_ACTIONS}:
                    risky.append(a)
            return risky

        def find_risky_perms_in_policy(document):
            risky_statements = []
            statements = document.get("Statement", [])
            if isinstance(statements, dict):
                statements = [statements]
            for stmt in statements:
                risky = statement_contains_risky_actions(stmt)
                if risky:
                    risky_statements.append((stmt, risky))
            return risky_statements
        ```

        Usage: loop through roles and attached policies:

        ```python theme={null}
        def audit_roles_for_priv_esc():
            roles = list_all_roles()
            findings = []

            for role in roles:
                role_name = role['RoleName']

                # Managed policies
                attached = list_attached_policies_for_role(role_name)
                for ap in attached:
                    doc = get_policy_document_arn(ap['PolicyArn'])
                    risky_stmts = find_risky_perms_in_policy(doc)
                    if risky_stmts:
                        findings.append({
                            "PrincipalType": "Role",
                            "PrincipalName": role_name,
                            "PolicyType": "Managed",
                            "PolicyArn": ap['PolicyArn'],
                            "RiskyStatements": risky_stmts,
                        })

                # Inline policies
                inline_names = list_inline_policies_for_role(role_name)
                for pname in inline_names:
                    doc = get_inline_policy_document(role_name, pname)
                    risky_stmts = find_risky_perms_in_policy(doc)
                    if risky_stmts:
                        findings.append({
                            "PrincipalType": "Role",
                            "PrincipalName": role_name,
                            "PolicyType": "Inline",
                            "PolicyName": pname,
                            "RiskyStatements": risky_stmts,
                        })

            return findings

        if __name__ == "__main__":
            findings = audit_roles_for_priv_esc()
            print(json.dumps(findings, default=str, indent=2))
        ```

        This identifies where privilege‑escalation‑prone actions exist.

        ***

        ## 4. Decide on Remediation Strategy (Per Finding)

        Per role/policy combination, you should:

        1. **Confirm business need** for each risky action.
        2. If **not needed**:
           * Remove or narrow the statement.
        3. If **needed**:
           * Restrict resources.
           * Add IAM conditions.
           * Use permission boundaries and/or SCPs.
           * Separate “break‑glass” roles from normal operators.

        Below are concrete policy changes and how to apply them via Python.

        ***

        ## 5. Remediate Excessive `iam:PassRole`

        ### 5.1 Policy Before (too broad)

        ```json theme={null}
        {
          "Effect": "Allow",
          "Action": "iam:PassRole",
          "Resource": "*"
        }
        ```

        ### 5.2 Policy After (restricted roles + conditions)

        ```json theme={null}
        {
          "Effect": "Allow",
          "Action": "iam:PassRole",
          "Resource": [
            "arn:aws:iam::123456789012:role/SpecificServiceRole"
          ],
          "Condition": {
            "StringEquals": {
              "iam:PassedToService": "lambda.amazonaws.com"
            }
          }
        }
        ```

        ### 5.3 Update Policy Version via Python

        For **managed policies**:

        ```python theme={null}
        def create_restricted_policy_version(policy_arn, new_document):
            resp = iam.create_policy_version(
                PolicyArn=policy_arn,
                PolicyDocument=json.dumps(new_document),
                SetAsDefault=True
            )
            return resp['PolicyVersion']['VersionId']
        ```

        Typical flow:

        1. Get existing document.
        2. Manually or programmatically modify statements (e.g., change `Resource: "*"` to specific ARNs; add `Condition`).
        3. Call `create_policy_version` with `SetAsDefault=True`.
        4. Optionally delete old versions later (max 5).

        ***

        ## 6. Remove or Restrict Delegated Policy Management (`iam:*Policy*`)

        If a role has permissions like `iam:CreatePolicyVersion`, `iam:PutRolePolicy`, etc., and it’s not a dedicated IAM admin role:

        1. **Create a new, least‑privilege IAM admin role** for identity team only.
        2. Remove those actions from operational roles.

        Sample adjustment:

        **Before:**

        ```json theme={null}
        {
          "Effect": "Allow",
          "Action": [
            "iam:CreatePolicyVersion",
            "iam:PutRolePolicy",
            "iam:AttachRolePolicy"
          ],
          "Resource": "*"
        }
        ```

        **After (if minimal is needed):**

        If the role only needs to attach a **pre‑approved** policy to a **specific role**:

        ```json theme={null}
        {
          "Effect": "Allow",
          "Action": [
            "iam:AttachRolePolicy"
          ],
          "Resource": "arn:aws:iam::123456789012:role/SpecificRole",
          "Condition": {
            "StringEquals": {
              "iam:PolicyARN": "arn:aws:iam::123456789012:policy/PreApprovedPolicy"
            }
          }
        }
        ```

        Apply via same `create_policy_version` mechanism.

        For **inline policies**, update with `put_role_policy`:

        ```python theme={null}
        def update_inline_policy(role_name, policy_name, new_document):
            iam.put_role_policy(
                RoleName=role_name,
                PolicyName=policy_name,
                PolicyDocument=json.dumps(new_document)
            )
        ```

        ***

        ## 7. Constrain `sts:AssumeRole` and Trust Policies

        Even if role A doesn’t have broad `iam:*`, it can escalate by assuming a more‑privileged role B.

        ### 7.1 Inspect trust policies

        ```python theme={null}
        def get_role_trust_policy(role_name):
            role = iam.get_role(RoleName=role_name)
            return role['Role']['AssumeRolePolicyDocument']

        def update_role_trust_policy(role_name, new_trust_doc):
            iam.update_assume_role_policy(
                RoleName=role_name,
                PolicyDocument=json.dumps(new_trust_doc)
            )
        ```

        ### 7.2 Restrict trust policies

        * Use specific principals, not `*`.
        * Add conditions like `aws:PrincipalTag`, `aws:SourceAccount`, `aws:SourceArn`.

        **Before (too open):**

        ```json theme={null}
        {
          "Effect": "Allow",
          "Principal": { "AWS": "*" },
          "Action": "sts:AssumeRole"
        }
        ```

        **After (restricted):**

        ```json theme={null}
        {
          "Effect": "Allow",
          "Principal": { "AWS": "arn:aws:iam::123456789012:role/SpecificCallerRole" },
          "Action": "sts:AssumeRole"
        }
        ```

        Or with conditions:

        ```json theme={null}
        {
          "Effect": "Allow",
          "Principal": { "AWS": "*" },
          "Action": "sts:AssumeRole",
          "Condition": {
            "StringEquals": { "aws:PrincipalTag/RoleType": "Automation" }
          }
        }
        ```

        ***

        ## 8. Guardrails: Permission Boundaries and SCPs (Org‑Level)

        ### 8.1 Permission Boundaries (per principal)

        Create a boundary policy that **forbids** escalation actions (or limits them tightly), then attach it as a permission boundary to roles/users that you do not want to be able to escalate.

        Example permission boundary:

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Deny",
              "Action": [
                "iam:CreatePolicyVersion",
                "iam:PutUserPolicy",
                "iam:PutRolePolicy",
                "iam:AttachUserPolicy",
                "iam:AttachRolePolicy",
                "iam:PassRole"
              ],
              "Resource": "*"
            }
          ]
        }
        ```

        Attach via Python:

        ```python theme={null}
        def attach_permission_boundary_to_role(role_name, boundary_arn):
            iam.update_role(
                RoleName=role_name,
                PermissionsBoundary=boundary_arn
            )
        ```

        ### 8.2 Service Control Policies (SCPs) (Org‑wide)

        In AWS Organizations, SCPs can deny dangerous actions account‑wide except for a specific IAM admin role.

        Example (pseudo; must be applied via Organizations API/console):

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Deny",
              "Action": [
                "iam:CreatePolicyVersion",
                "iam:PutRolePolicy",
                "iam:AttachRolePolicy",
                "iam:PassRole"
              ],
              "Resource": "*",
              "Condition": {
                "StringNotEquals": {
                  "aws:PrincipalArn": "arn:aws:iam::123456789012:role/CentralIAMAdmin"
                }
              }
            }
          ]
        }
        ```

        ***

        ## 9. Special Case: Service‑Based Escalation (Lambda, EC2, etc.)

        Even if `iam:PassRole` is restricted, some combos can still escalate; for each:

        * Lambda: `lambda:UpdateFunctionConfiguration` to change `Role`.
        * EC2: `ec2:RunInstances` with a modifiable instance profile.
        * Step Functions: `states:CreateStateMachine` with high‑priv role.

        **Remediation pattern:**

        * Constrain `Resource` to specific functions, instances, state machines.
        * Combine with **tight** `iam:PassRole` (only low‑priv roles).
        * Use conditions like `iam:PassedToService`, `ec2:InstanceProfile`, or tags.

        Example for Lambda:

        ```json theme={null}
        {
          "Effect": "Allow",
          "Action": [
            "lambda:UpdateFunctionConfiguration"
          ],
          "Resource": "arn:aws:lambda:us-east-1:123456789012:function:MyApp-*",
          "Condition": {
            "StringEquals": {
              "lambda:Role": "arn:aws:iam::123456789012:role/MyAppExecutionRole"
            }
          }
        }
        ```

        ***

        ## 10. Verification / Regression Checks

        After changes:

        1. **Use IAM Access Analyzer** (console or API) to detect potential privilege escalation.
        2. Re‑run your Python audit script to ensure:
           * No unexpected `iam:*Policy*` / `iam:PassRole` / `sts:AssumeRole` wildcards.
           * No `Action: "*"` + `Resource: "*"` for non‑admin roles.
        3. Test critical workflows to confirm business operations continue to function.

        ***

        If you want, I can:

        * Help you adapt the audit script to automatically suggest a “patched” policy document, or
        * Focus on a specific privileged role’s policies and walk through concrete JSON diffs.
      </Accordion>

      <Accordion title="Using Terraform">
        ```hcl theme={null}
        # IAM policy with privilege‑escalation actions removed or tightly scoped
        resource "aws_iam_policy" "restricted_role_management" {
          name        = "restricted-role-management"
          description = "Allows only tightly scoped role management actions without privilege escalation"

          # Replace with the ARN of the specific role this workload is allowed to pass
          # e.g. arn:aws:iam::123456789012:role/my-allowed-target-role
          # Substitute YOUR_ACCOUNT_ID and YOUR_ROLE_NAME as needed.
          policy = jsonencode({
            Version = "2012-10-17"
            Statement = [
              # Example: allow passing only a specific role, and only to a specific service
              {
                Sid    = "AllowPassSpecificRoleToSpecificService"
                Effect = "Allow"
                Action = [
                  "iam:PassRole"
                ]
                Resource = "arn:aws:iam::YOUR_ACCOUNT_ID:role/YOUR_ALLOWED_TARGET_ROLE"
                Condition = {
                  StringEquals = {
                    "iam:PassedToService" = "ecs-tasks.amazonaws.com" # SUBSTITUTE TARGET SERVICE
                  }
                }
              },

              # Example: allow listing/reading IAM metadata, which is not escalating
              {
                Sid    = "AllowIamReadOnly"
                Effect = "Allow"
                Action = [
                  "iam:GetRole",
                  "iam:ListRolePolicies",
                  "iam:ListAttachedRolePolicies",
                  "iam:ListRoles"
                ]
                Resource = "*"
              }

              # NOTE:
              #  - DO NOT include broad write actions like:
              #    iam:AttachRolePolicy
              #    iam:PutRolePolicy
              #    iam:CreatePolicy
              #    iam:CreatePolicyVersion
              #    iam:SetDefaultPolicyVersion
              #    iam:UpdateAssumeRolePolicy
              #    iam:AddUserToGroup
              #    iam:UpdateLoginProfile
              #    sts:AssumeRole on higher-privilege roles
              #  - If such actions are currently in the JSON, remove them or scope them
              #    to the exact roles/policies this workload must manage, with explicit ARNs.
            ]
          })
        }

        # Attach the remediated policy to the role that previously had an escalating policy
        resource "aws_iam_role_policy_attachment" "role_restricted_attachment" {
          role       = aws_iam_role.TARGET_ROLE.name             # SUBSTITUTE EXISTING ROLE
          policy_arn = aws_iam_policy.restricted_role_management.arn
        }

        # Example role showing how it would be referenced (if not already defined)
        resource "aws_iam_role" "TARGET_ROLE" {
          name = "YOUR_EXISTING_ROLE_NAME" # SUBSTITUTE; align with actual role name

          assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
        }

        data "aws_iam_policy_document" "assume_role_policy" {
          statement {
            effect  = "Allow"
            actions = ["sts:AssumeRole"]

            principals {
              type        = "Service"
              identifiers = ["ecs-tasks.amazonaws.com"] # SUBSTITUTE CALLER SERVICE
            }
          }
        }
        ```

        Changing an existing `aws_iam_policy` in Terraform (rather than editing inline JSON only) forces replacement of the policy resource; any existing attachments will be recreated by Terraform, which may briefly remove and reattach the policy during `apply`.

        For verification, `terraform plan` should show: removal of the old IAM policy resource (with privilege‑escalation actions), creation of the new restricted `aws_iam_policy`, and updates to `aws_iam_role_policy_attachment` resources so that the role is attached only to the remediated policy.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
