Skip to content

Instantly share code, notes, and snippets.

@varunchandak
Created January 27, 2025 06:32
Show Gist options
  • Save varunchandak/bf9ed5c6be9410139bdcf054f55777db to your computer and use it in GitHub Desktop.
Save varunchandak/bf9ed5c6be9410139bdcf054f55777db to your computer and use it in GitHub Desktop.
IAM Inline to Managed Policy Migration Script

IAM Inline to Managed Policy Migration Script

Overview

This Python script automates the process of converting inline IAM policies attached to roles into managed IAM policies. It ensures efficient policy reuse by identifying identical inline policies and linking them to a single managed policy. Additionally, it resolves conflicts in policy naming by appending unique timestamps when necessary.


Features

  1. Inline to Managed Policy Conversion:
    • Migrates inline policies attached to roles into managed policies.
  2. Policy Deduplication:
    • Ensures identical inline policies are converted to a single managed policy.
  3. Conflict Resolution:
    • Handles duplicate managed policy names by appending a unique timestamp.
  4. Final Validation:
    • Verifies that all inline policies are removed from the specified roles.

Prerequisites

  1. AWS CLI:
    • Ensure you have the AWS CLI installed and configured with appropriate permissions.
    • Install AWS CLI
  2. Boto3:
    • Install the AWS SDK for Python using pip:
      pip install boto3
  3. Permissions:
    • The script requires the following IAM permissions:
      • iam:ListRolePolicies
      • iam:GetRolePolicy
      • iam:DeleteRolePolicy
      • iam:ListPolicies
      • iam:GetPolicy
      • iam:GetPolicyVersion
      • iam:CreatePolicy
      • iam:AttachRolePolicy

How to Use

  1. Clone the Script:

    • Copy the script into a local .py file, e.g., role-inline-to-managed.py.
  2. Run the Script:

    • Provide a comma-separated list of role ARNs when prompted.
    • Example:
      python role-inline-to-managed.py
      Enter comma-separated role ARNs: arn:aws:iam::123456789012:role/MyRole1,arn:aws:iam::123456789012:role/MyRole2
  3. Script Workflow:

    • The script performs the following steps for each role:
      1. Validation: Ensures role ARNs are in the correct format.
      2. Fetch Inline Policies: Retrieves all inline policies attached to the role.
      3. Policy Deduplication: Computes a hash of each policy document to check for existing identical managed policies.
      4. Managed Policy Creation or Reuse:
        • If no existing policy matches, a new managed policy is created.
        • Handles name conflicts by appending a unique timestamp.
      5. Attachment and Removal:
        • Attaches the managed policy to the role.
        • Removes the original inline policy.
      6. Final Validation: Ensures the role has no remaining inline policies.

Example Output

Successful Conversion

Processing role: MyRole1
Found 2 inline policies for role MyRole1
Processing inline policy: Policy1
Created managed policy: arn:aws:iam::123456789012:policy/Policy1
Attached managed policy arn:aws:iam::123456789012:policy/Policy1 to role MyRole1
Removed inline policy: Policy1
All inline policies removed for role MyRole1

Policy Reuse

Processing inline policy: Policy2
Reusing existing managed policy: arn:aws:iam::123456789012:policy/Policy2
Attached managed policy arn:aws:iam::123456789012:policy/Policy2 to role MyRole2
Removed inline policy: Policy2

Notes

  1. IAM Policy Limits:

    • Managed policies have limits on the number of characters, statements, and conditions. Ensure inline policies are within these limits.
    • IAM Policy Quotas
  2. Error Handling:

    • The script gracefully handles duplicate managed policy names and ensures unique naming.
    • In case of permission issues or other errors, the script stops execution and displays the error message.
  3. Testing:

    • It is recommended to test the script in a non-production environment to ensure it works as expected before running on production roles.

License

This script is provided under the MIT License. Feel free to use, modify, and distribute it.

import boto3
import json
import re
import hashlib
import time
# Initialize the AWS client
iam_client = boto3.client('iam')
def validate_role_arns(role_arns):
"""Validate the format of role ARNs."""
arn_pattern = r"^arn:aws:iam::\d{12}:role\/[\w+=,.@-]+$"
for arn in role_arns:
if not re.match(arn_pattern, arn):
raise ValueError(f"Invalid role ARN format: {arn}")
def get_inline_policies(role_name):
"""Fetch inline policies for a given role."""
response = iam_client.list_role_policies(RoleName=role_name)
return response.get('PolicyNames', [])
def hash_policy_document(policy_document):
"""Create a hash of the policy document for deduplication."""
policy_json = json.dumps(policy_document, sort_keys=True)
return hashlib.sha256(policy_json.encode('utf-8')).hexdigest()
def find_existing_managed_policy(policy_hash):
"""Check for an existing managed policy with the same hash."""
paginator = iam_client.get_paginator('list_policies')
for page in paginator.paginate(Scope='Local'):
for policy in page['Policies']:
policy_description = iam_client.get_policy(PolicyArn=policy['Arn'])
version_id = policy_description['Policy']['DefaultVersionId']
version = iam_client.get_policy_version(
PolicyArn=policy['Arn'],
VersionId=version_id
)
existing_policy_document = version['PolicyVersion']['Document']
if hash_policy_document(existing_policy_document) == policy_hash:
return policy['Arn']
return None
def create_unique_policy_name(base_name):
"""Generate a unique policy name to handle conflicts."""
timestamp = int(time.time())
return f"{base_name}-{timestamp}"
def create_managed_policy(policy_name, policy_document):
"""Create a managed policy from an inline policy."""
try:
response = iam_client.create_policy(
PolicyName=policy_name,
PolicyDocument=json.dumps(policy_document)
)
return response['Policy']['Arn']
except iam_client.exceptions.EntityAlreadyExistsException:
# Handle duplicate policy names by appending a timestamp
unique_name = create_unique_policy_name(policy_name)
response = iam_client.create_policy(
PolicyName=unique_name,
PolicyDocument=json.dumps(policy_document)
)
return response['Policy']['Arn']
def attach_managed_policy(role_name, policy_arn):
"""Attach a managed policy to a role."""
iam_client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
def remove_inline_policy(role_name, policy_name):
"""Remove an inline policy from a role."""
iam_client.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
def ensure_no_inline_policies(role_name):
"""Check that no inline policies remain for a role."""
inline_policies = get_inline_policies(role_name)
if inline_policies:
raise Exception(f"Inline policies still present for role {role_name}: {inline_policies}")
def process_roles(role_arns):
"""Main function to process roles."""
validate_role_arns(role_arns)
for role_arn in role_arns:
role_name = role_arn.split('/')[-1]
print(f"Processing role: {role_name}")
# Get inline policies
inline_policies = get_inline_policies(role_name)
print(f"Found {len(inline_policies)} inline policies for role {role_name}")
for policy_name in inline_policies:
print(f"Processing inline policy: {policy_name}")
# Get policy document
response = iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
policy_document = response['PolicyDocument']
# Hash the policy document
policy_hash = hash_policy_document(policy_document)
existing_policy_arn = find_existing_managed_policy(policy_hash)
if existing_policy_arn:
print(f"Reusing existing managed policy: {existing_policy_arn}")
managed_policy_arn = existing_policy_arn
else:
# Create managed policy
managed_policy_arn = create_managed_policy(policy_name, policy_document)
print(f"Created managed policy: {managed_policy_arn}")
# Attach managed policy to role
attach_managed_policy(role_name, managed_policy_arn)
print(f"Attached managed policy {managed_policy_arn} to role {role_name}")
# Remove inline policy
remove_inline_policy(role_name, policy_name)
print(f"Removed inline policy: {policy_name}")
# Final check
ensure_no_inline_policies(role_name)
print(f"All inline policies removed for role {role_name}")
if __name__ == "__main__":
# Take input of comma-separated role ARNs
role_arns_input = input("Enter comma-separated role ARNs: ")
role_arns = [arn.strip() for arn in role_arns_input.split(",")]
try:
process_roles(role_arns)
print("All roles processed successfully.")
except Exception as e:
print(f"Error: {e}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment