From 123294eb7b88001d15d97295a002d3683d70fd1b Mon Sep 17 00:00:00 2001 From: Seth Art Date: Tue, 9 Sep 2025 10:20:16 -0400 Subject: [PATCH] Add cleanup scripts for when Terraform state is lost - Add Python and Bash versions of cleanup scripts - Scripts automatically identify and delete all iam-vulnerable resources - Update main README.md with cleanup script usage instructions - Upgrade Terraform AWS provider version to ~> 4.9 - Updated nodejs version for lambda --- README.md | 20 + cleanup-scripts/CLEANUP_README.md | 131 +++ cleanup-scripts/cleanup_iam_vulnerable.py | 511 +++++++++++ cleanup-scripts/cleanup_iam_vulnerable.sh | 856 ++++++++++++++++++ main.tf | 6 +- modules/non-free-resources/lambda/lambda.tf | 2 +- .../lambda/lambda_function.zip | Bin 240 -> 238 bytes 7 files changed, 1522 insertions(+), 4 deletions(-) create mode 100644 cleanup-scripts/CLEANUP_README.md create mode 100755 cleanup-scripts/cleanup_iam_vulnerable.py create mode 100755 cleanup-scripts/cleanup_iam_vulnerable.sh diff --git a/README.md b/README.md index 0727b1f..290efe9 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,26 @@ Whenever you want to remove all of the IAM Vulnerable-created resources, you can 1. `cd iam-vulnerable/` 1. `terraform destroy` +**Alternative Cleanup (When Terraform State is Lost)** + +In a case where you have deployed iam-vulnerable using Terraform but no longer have access to the state file (and `terraform destroy` does not work), you can use the following cleanup scripts: + +```bash +# Python version (requires boto3) +./cleanup-scripts/cleanup_iam_vulnerable.py --dry-run + +# Bash version (requires AWS CLI and jq) +./cleanup-scripts/cleanup_iam_vulnerable.sh --dry-run +``` + +These scripts will: +- Automatically identify all IAM Vulnerable resources in your AWS account +- Show you exactly what will be deleted before proceeding +- Delete resources in the proper order to avoid dependency conflicts +- Support AWS profiles and provide detailed logging + +**Important**: Always run with `--dry-run` first to see what would be deleted. See `cleanup-scripts/CLEANUP_README.md` for detailed usage instructions. + ## What resources were just created? diff --git a/cleanup-scripts/CLEANUP_README.md b/cleanup-scripts/CLEANUP_README.md new file mode 100644 index 0000000..226b842 --- /dev/null +++ b/cleanup-scripts/CLEANUP_README.md @@ -0,0 +1,131 @@ +# IAM Vulnerable Cleanup Scripts + +This directory contains two cleanup scripts designed to remove all resources created by the iam-vulnerable Terraform project. These scripts are particularly useful when the Terraform state is lost and `terraform destroy` cannot be used. + +## Scripts Available + +### 1. Python Version (`cleanup_iam_vulnerable.py`) +- **Requirements**: Python 3.6+, boto3 library +- **Installation**: `pip install boto3` +- **Features**: Rich output formatting, detailed error handling, comprehensive resource detection + +### 2. Bash Version (`cleanup_iam_vulnerable.sh`) +- **Requirements**: AWS CLI v2, jq +- **Installation**: + - AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html + - jq: `brew install jq` (macOS) or `apt-get install jq` (Ubuntu) +- **Features**: Lightweight, no Python dependencies, colorized output + +## Usage + +Both scripts support the same command-line options: + +```bash +# Python version +./cleanup_iam_vulnerable.py [--profile PROFILE_NAME] [--dry-run] [--yes] + +# Bash version +./cleanup_iam_vulnerable.sh [--profile PROFILE_NAME] [--dry-run] [--yes] +``` + +### Options + +- `--profile PROFILE_NAME`: Use a specific AWS profile (default: uses default profile or environment variables) +- `--dry-run`: Show what would be deleted without actually deleting anything +- `--yes`: Skip the confirmation prompt and proceed with deletion + +### Examples + +```bash +# Dry run to see what would be deleted +./cleanup_iam_vulnerable.py --dry-run + +# Use a specific AWS profile +./cleanup_iam_vulnerable.py --profile my-aws-profile + +# Skip confirmation and delete everything +./cleanup_iam_vulnerable.py --yes + +# Combine options +./cleanup_iam_vulnerable.sh --profile staging --dry-run +``` + +## Resources Cleaned Up + +The scripts will identify and delete the following types of resources created by iam-vulnerable: + +### IAM Resources +- **Users**: All users with names starting with `privesc`, `fn1-`, `fn2-`, `fn3-`, `fn4-`, `fp1-`, `fp2-`, `fp3-`, `fp4-`, `fp5-`, or named `Ryan` +- **Roles**: All roles matching the same naming patterns +- **Groups**: All groups matching the same naming patterns +- **Policies**: All customer-managed policies matching the same naming patterns +- **Access Keys**: All access keys belonging to the identified users +- **Instance Profiles**: All instance profiles matching the naming patterns + +### Other AWS Resources +- **Lambda Functions**: Functions named `test_lambda` or matching the naming patterns +- **CloudFormation Stacks**: Stacks named `privesc-cloudformationStack` or matching patterns +- **Glue Dev Endpoints**: Endpoints named `privesc-glue-devendpoint` or matching patterns +- **SageMaker Notebooks**: Notebooks named `privesc-sagemakerNotebook` or matching patterns + +## Deletion Order + +The scripts delete resources in a specific order to avoid dependency conflicts: + +1. Access Keys +2. SageMaker Notebook Instances (stopped first if running) +3. Glue Development Endpoints +4. Lambda Functions +5. CloudFormation Stacks (deletion initiated, may take time to complete) +6. Instance Profiles (roles removed first) +7. IAM Users (policies detached, groups removed, login profiles deleted) +8. IAM Groups (policies detached) +9. IAM Roles (policies detached) +10. IAM Policies (non-default versions deleted first) + +## Safety Features + +- **Confirmation Prompt**: By default, scripts show all resources to be deleted and require explicit confirmation +- **Dry Run Mode**: Use `--dry-run` to see what would be deleted without making any changes +- **Error Handling**: Scripts continue processing even if individual resource deletions fail +- **Detailed Logging**: Clear output showing what's being deleted and any errors encountered + +## AWS Credentials + +The scripts use standard AWS credential resolution: + +1. Command line profile (`--profile` option) +2. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, etc.) +3. AWS credentials file (`~/.aws/credentials`) +4. IAM roles (if running on EC2) + +## Important Notes + +- **Irreversible**: Resource deletion cannot be undone +- **CloudFormation**: Stack deletions are initiated but may take several minutes to complete +- **SageMaker**: Notebook instances must be stopped before deletion (script handles this automatically) +- **Permissions**: Ensure your AWS credentials have sufficient permissions to delete all resource types + +## Troubleshooting + +### Common Issues + +1. **Permission Denied**: Ensure your AWS credentials have the necessary IAM permissions +2. **Resource Not Found**: Some resources may have already been deleted or may not exist +3. **Dependency Conflicts**: The scripts handle most dependency issues automatically, but some resources may need manual intervention + +### Getting Help + +Run either script with `--help` to see usage information: + +```bash +./cleanup_iam_vulnerable.py --help +./cleanup_iam_vulnerable.sh --help +``` + +## Security Considerations + +- These scripts have broad permissions to delete AWS resources +- Always run with `--dry-run` first to verify what will be deleted +- Use specific AWS profiles to limit scope to the intended account +- Review the resource list carefully before confirming deletion diff --git a/cleanup-scripts/cleanup_iam_vulnerable.py b/cleanup-scripts/cleanup_iam_vulnerable.py new file mode 100755 index 0000000..02fd021 --- /dev/null +++ b/cleanup-scripts/cleanup_iam_vulnerable.py @@ -0,0 +1,511 @@ +#!/usr/bin/env python3 +""" +IAM Vulnerable Cleanup Script (Python Version) + +This script cleans up all resources created by the iam-vulnerable Terraform project. +It's designed for cases where the Terraform state is lost and 'terraform destroy' cannot be used. + +Usage: + python3 cleanup_iam_vulnerable.py [--profile PROFILE_NAME] [--dry-run] [--yes] + +Requirements: + - boto3 (pip install boto3) + - AWS credentials configured (via profile, environment variables, or IAM role) +""" + +import boto3 +import sys +import argparse +import json +from botocore.exceptions import ClientError, NoCredentialsError +from typing import List, Dict, Any + +class IAMVulnerableCleanup: + def __init__(self, profile_name: str = None, dry_run: bool = False): + """Initialize the cleanup script with AWS session.""" + try: + if profile_name: + self.session = boto3.Session(profile_name=profile_name) + else: + self.session = boto3.Session() + + self.iam = self.session.client('iam') + self.lambda_client = self.session.client('lambda') + self.cloudformation = self.session.client('cloudformation') + self.glue = self.session.client('glue') + self.sagemaker = self.session.client('sagemaker') + self.sts = self.session.client('sts') + + self.dry_run = dry_run + self.account_id = None + + except NoCredentialsError: + print("❌ Error: AWS credentials not found. Please configure your credentials.") + sys.exit(1) + except Exception as e: + print(f"❌ Error initializing AWS session: {e}") + sys.exit(1) + + def get_account_info(self) -> Dict[str, str]: + """Get AWS account information.""" + try: + identity = self.sts.get_caller_identity() + self.account_id = identity['Account'] + return { + 'account_id': identity['Account'], + 'user_id': identity['UserId'], + 'arn': identity['Arn'] + } + except ClientError as e: + print(f"❌ Error getting account information: {e}") + sys.exit(1) + + def find_iam_vulnerable_resources(self) -> Dict[str, List[Dict[str, Any]]]: + """Find all IAM resources created by iam-vulnerable.""" + resources = { + 'users': [], + 'roles': [], + 'groups': [], + 'policies': [], + 'access_keys': [], + 'instance_profiles': [], + 'lambda_functions': [], + 'cloudformation_stacks': [], + 'glue_dev_endpoints': [], + 'sagemaker_notebooks': [] + } + + # Define prefixes and patterns used by iam-vulnerable + iam_prefixes = [ + 'privesc', 'fn1-', 'fn2-', 'fn3-', 'fn4-', 'fp1-', 'fp2-', 'fp3-', 'fp4-', 'fp5-', + 'Ryan' # Special admin user + ] + + print("πŸ” Scanning for IAM Vulnerable resources...") + + # Find IAM Users + try: + paginator = self.iam.get_paginator('list_users') + for page in paginator.paginate(): + for user in page['Users']: + user_name = user['UserName'] + if any(user_name.startswith(prefix) for prefix in iam_prefixes): + resources['users'].append(user) + + # Find access keys for this user + try: + keys_response = self.iam.list_access_keys(UserName=user_name) + for key in keys_response['AccessKeyMetadata']: + resources['access_keys'].append({ + 'AccessKeyId': key['AccessKeyId'], + 'UserName': user_name, + 'Status': key['Status'] + }) + except ClientError: + pass + except ClientError as e: + print(f"⚠️ Warning: Could not list users: {e}") + + # Find IAM Roles + try: + paginator = self.iam.get_paginator('list_roles') + for page in paginator.paginate(): + for role in page['Roles']: + role_name = role['RoleName'] + if any(role_name.startswith(prefix) for prefix in iam_prefixes): + resources['roles'].append(role) + except ClientError as e: + print(f"⚠️ Warning: Could not list roles: {e}") + + # Find IAM Groups + try: + paginator = self.iam.get_paginator('list_groups') + for page in paginator.paginate(): + for group in page['Groups']: + group_name = group['GroupName'] + if any(group_name.startswith(prefix) for prefix in iam_prefixes): + resources['groups'].append(group) + except ClientError as e: + print(f"⚠️ Warning: Could not list groups: {e}") + + # Find IAM Policies + try: + paginator = self.iam.get_paginator('list_policies') + for page in paginator.paginate(Scope='Local'): # Only customer-managed policies + for policy in page['Policies']: + policy_name = policy['PolicyName'] + if any(policy_name.startswith(prefix) for prefix in iam_prefixes): + resources['policies'].append(policy) + except ClientError as e: + print(f"⚠️ Warning: Could not list policies: {e}") + + # Find Instance Profiles + try: + paginator = self.iam.get_paginator('list_instance_profiles') + for page in paginator.paginate(): + for profile in page['InstanceProfiles']: + profile_name = profile['InstanceProfileName'] + if any(profile_name.startswith(prefix) for prefix in iam_prefixes): + resources['instance_profiles'].append(profile) + except ClientError as e: + print(f"⚠️ Warning: Could not list instance profiles: {e}") + + # Find Lambda Functions + try: + paginator = self.lambda_client.get_paginator('list_functions') + for page in paginator.paginate(): + for function in page['Functions']: + function_name = function['FunctionName'] + if function_name == 'test_lambda' or any(function_name.startswith(prefix) for prefix in iam_prefixes): + resources['lambda_functions'].append(function) + except ClientError as e: + print(f"⚠️ Warning: Could not list Lambda functions: {e}") + + # Find CloudFormation Stacks + try: + paginator = self.cloudformation.get_paginator('list_stacks') + for page in paginator.paginate(StackStatusFilter=['CREATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_COMPLETE']): + for stack in page['StackSummaries']: + stack_name = stack['StackName'] + if stack_name == 'privesc-cloudformationStack' or any(stack_name.startswith(prefix) for prefix in iam_prefixes): + resources['cloudformation_stacks'].append(stack) + except ClientError as e: + print(f"⚠️ Warning: Could not list CloudFormation stacks: {e}") + + # Find Glue Dev Endpoints + try: + response = self.glue.get_dev_endpoints() + for endpoint in response['DevEndpoints']: + endpoint_name = endpoint['EndpointName'] + if endpoint_name == 'privesc-glue-devendpoint' or any(endpoint_name.startswith(prefix) for prefix in iam_prefixes): + resources['glue_dev_endpoints'].append(endpoint) + except ClientError as e: + print(f"⚠️ Warning: Could not list Glue dev endpoints: {e}") + + # Find SageMaker Notebook Instances + try: + paginator = self.sagemaker.get_paginator('list_notebook_instances') + for page in paginator.paginate(): + for notebook in page['NotebookInstances']: + notebook_name = notebook['NotebookInstanceName'] + if notebook_name == 'privesc-sagemakerNotebook' or any(notebook_name.startswith(prefix) for prefix in iam_prefixes): + resources['sagemaker_notebooks'].append(notebook) + except ClientError as e: + print(f"⚠️ Warning: Could not list SageMaker notebooks: {e}") + + return resources + + def display_resources(self, resources: Dict[str, List[Dict[str, Any]]]) -> int: + """Display all found resources and return total count.""" + total_count = 0 + + print("\n" + "="*60) + print("πŸ“‹ IAM VULNERABLE RESOURCES FOUND") + print("="*60) + + for resource_type, items in resources.items(): + if items: + count = len(items) + total_count += count + print(f"\nπŸ”Έ {resource_type.upper().replace('_', ' ')} ({count}):") + + for item in items: + if resource_type == 'users': + print(f" β€’ {item['UserName']} (Created: {item['CreateDate'].strftime('%Y-%m-%d %H:%M:%S')})") + elif resource_type == 'roles': + print(f" β€’ {item['RoleName']} (Created: {item['CreateDate'].strftime('%Y-%m-%d %H:%M:%S')})") + elif resource_type == 'groups': + print(f" β€’ {item['GroupName']} (Created: {item['CreateDate'].strftime('%Y-%m-%d %H:%M:%S')})") + elif resource_type == 'policies': + print(f" β€’ {item['PolicyName']} (ARN: {item['Arn']})") + elif resource_type == 'access_keys': + print(f" β€’ {item['AccessKeyId']} (User: {item['UserName']}, Status: {item['Status']})") + elif resource_type == 'instance_profiles': + print(f" β€’ {item['InstanceProfileName']} (Created: {item['CreateDate'].strftime('%Y-%m-%d %H:%M:%S')})") + elif resource_type == 'lambda_functions': + print(f" β€’ {item['FunctionName']} (Runtime: {item['Runtime']})") + elif resource_type == 'cloudformation_stacks': + print(f" β€’ {item['StackName']} (Status: {item['StackStatus']})") + elif resource_type == 'glue_dev_endpoints': + print(f" β€’ {item['EndpointName']} (Status: {item.get('Status', 'Unknown')})") + elif resource_type == 'sagemaker_notebooks': + print(f" β€’ {item['NotebookInstanceName']} (Status: {item['NotebookInstanceStatus']})") + + print(f"\nπŸ“Š TOTAL RESOURCES: {total_count}") + return total_count + + def delete_resources(self, resources: Dict[str, List[Dict[str, Any]]]) -> None: + """Delete all found resources in the correct order.""" + if self.dry_run: + print("\nπŸ” DRY RUN MODE - No resources will be deleted") + return + + print("\nπŸ—‘οΈ Starting resource deletion...") + + # Deletion order is important to avoid dependency conflicts + deletion_order = [ + ('access_keys', self._delete_access_keys), + ('sagemaker_notebooks', self._delete_sagemaker_notebooks), + ('glue_dev_endpoints', self._delete_glue_dev_endpoints), + ('lambda_functions', self._delete_lambda_functions), + ('cloudformation_stacks', self._delete_cloudformation_stacks), + ('instance_profiles', self._delete_instance_profiles), + ('users', self._delete_users), + ('groups', self._delete_groups), + ('roles', self._delete_roles), + ('policies', self._delete_policies) + ] + + for resource_type, delete_func in deletion_order: + if resources[resource_type]: + print(f"\nπŸ”Έ Deleting {resource_type.replace('_', ' ')}...") + delete_func(resources[resource_type]) + + def _delete_access_keys(self, access_keys: List[Dict[str, Any]]) -> None: + """Delete IAM access keys.""" + for key in access_keys: + try: + self.iam.delete_access_key( + UserName=key['UserName'], + AccessKeyId=key['AccessKeyId'] + ) + print(f" βœ… Deleted access key: {key['AccessKeyId']} (User: {key['UserName']})") + except ClientError as e: + print(f" ❌ Failed to delete access key {key['AccessKeyId']}: {e}") + + def _delete_users(self, users: List[Dict[str, Any]]) -> None: + """Delete IAM users and their attached policies.""" + for user in users: + user_name = user['UserName'] + try: + # Detach user policies + try: + attached_policies = self.iam.list_attached_user_policies(UserName=user_name) + for policy in attached_policies['AttachedPolicies']: + self.iam.detach_user_policy(UserName=user_name, PolicyArn=policy['PolicyArn']) + print(f" πŸ”— Detached policy {policy['PolicyName']} from user {user_name}") + except ClientError: + pass + + # Delete inline user policies + try: + inline_policies = self.iam.list_user_policies(UserName=user_name) + for policy_name in inline_policies['PolicyNames']: + self.iam.delete_user_policy(UserName=user_name, PolicyName=policy_name) + print(f" πŸ”— Deleted inline policy {policy_name} from user {user_name}") + except ClientError: + pass + + # Remove user from groups + try: + groups = self.iam.list_groups_for_user(UserName=user_name) + for group in groups['Groups']: + self.iam.remove_user_from_group(UserName=user_name, GroupName=group['GroupName']) + print(f" πŸ‘₯ Removed user {user_name} from group {group['GroupName']}") + except ClientError: + pass + + # Delete login profile if exists + try: + self.iam.delete_login_profile(UserName=user_name) + print(f" πŸ”‘ Deleted login profile for user {user_name}") + except ClientError: + pass + + # Delete the user + self.iam.delete_user(UserName=user_name) + print(f" βœ… Deleted user: {user_name}") + except ClientError as e: + print(f" ❌ Failed to delete user {user_name}: {e}") + + def _delete_groups(self, groups: List[Dict[str, Any]]) -> None: + """Delete IAM groups and their attached policies.""" + for group in groups: + group_name = group['GroupName'] + try: + # Detach group policies + try: + attached_policies = self.iam.list_attached_group_policies(GroupName=group_name) + for policy in attached_policies['AttachedPolicies']: + self.iam.detach_group_policy(GroupName=group_name, PolicyArn=policy['PolicyArn']) + print(f" πŸ”— Detached policy {policy['PolicyName']} from group {group_name}") + except ClientError: + pass + + # Delete inline group policies + try: + inline_policies = self.iam.list_group_policies(GroupName=group_name) + for policy_name in inline_policies['PolicyNames']: + self.iam.delete_group_policy(GroupName=group_name, PolicyName=policy_name) + print(f" πŸ”— Deleted inline policy {policy_name} from group {group_name}") + except ClientError: + pass + + # Delete the group + self.iam.delete_group(GroupName=group_name) + print(f" βœ… Deleted group: {group_name}") + except ClientError as e: + print(f" ❌ Failed to delete group {group_name}: {e}") + + def _delete_roles(self, roles: List[Dict[str, Any]]) -> None: + """Delete IAM roles and their attached policies.""" + for role in roles: + role_name = role['RoleName'] + try: + # Detach role policies + try: + attached_policies = self.iam.list_attached_role_policies(RoleName=role_name) + for policy in attached_policies['AttachedPolicies']: + self.iam.detach_role_policy(RoleName=role_name, PolicyArn=policy['PolicyArn']) + print(f" πŸ”— Detached policy {policy['PolicyName']} from role {role_name}") + except ClientError: + pass + + # Delete inline role policies + try: + inline_policies = self.iam.list_role_policies(RoleName=role_name) + for policy_name in inline_policies['PolicyNames']: + self.iam.delete_role_policy(RoleName=role_name, PolicyName=policy_name) + print(f" πŸ”— Deleted inline policy {policy_name} from role {role_name}") + except ClientError: + pass + + # Delete the role + self.iam.delete_role(RoleName=role_name) + print(f" βœ… Deleted role: {role_name}") + except ClientError as e: + print(f" ❌ Failed to delete role {role_name}: {e}") + + def _delete_policies(self, policies: List[Dict[str, Any]]) -> None: + """Delete IAM policies.""" + for policy in policies: + try: + # Delete all policy versions except the default + versions = self.iam.list_policy_versions(PolicyArn=policy['Arn']) + for version in versions['Versions']: + if not version['IsDefaultVersion']: + self.iam.delete_policy_version( + PolicyArn=policy['Arn'], + VersionId=version['VersionId'] + ) + print(f" πŸ“„ Deleted policy version {version['VersionId']} for {policy['PolicyName']}") + + # Delete the policy + self.iam.delete_policy(PolicyArn=policy['Arn']) + print(f" βœ… Deleted policy: {policy['PolicyName']}") + except ClientError as e: + print(f" ❌ Failed to delete policy {policy['PolicyName']}: {e}") + + def _delete_instance_profiles(self, profiles: List[Dict[str, Any]]) -> None: + """Delete IAM instance profiles.""" + for profile in profiles: + profile_name = profile['InstanceProfileName'] + try: + # Remove roles from instance profile + for role in profile['Roles']: + self.iam.remove_role_from_instance_profile( + InstanceProfileName=profile_name, + RoleName=role['RoleName'] + ) + print(f" πŸ”— Removed role {role['RoleName']} from instance profile {profile_name}") + + # Delete the instance profile + self.iam.delete_instance_profile(InstanceProfileName=profile_name) + print(f" βœ… Deleted instance profile: {profile_name}") + except ClientError as e: + print(f" ❌ Failed to delete instance profile {profile_name}: {e}") + + def _delete_lambda_functions(self, functions: List[Dict[str, Any]]) -> None: + """Delete Lambda functions.""" + for function in functions: + try: + self.lambda_client.delete_function(FunctionName=function['FunctionName']) + print(f" βœ… Deleted Lambda function: {function['FunctionName']}") + except ClientError as e: + print(f" ❌ Failed to delete Lambda function {function['FunctionName']}: {e}") + + def _delete_cloudformation_stacks(self, stacks: List[Dict[str, Any]]) -> None: + """Delete CloudFormation stacks.""" + for stack in stacks: + try: + self.cloudformation.delete_stack(StackName=stack['StackName']) + print(f" βœ… Initiated deletion of CloudFormation stack: {stack['StackName']}") + print(f" ⏳ Note: Stack deletion may take several minutes to complete") + except ClientError as e: + print(f" ❌ Failed to delete CloudFormation stack {stack['StackName']}: {e}") + + def _delete_glue_dev_endpoints(self, endpoints: List[Dict[str, Any]]) -> None: + """Delete Glue development endpoints.""" + for endpoint in endpoints: + try: + self.glue.delete_dev_endpoint(EndpointName=endpoint['EndpointName']) + print(f" βœ… Deleted Glue dev endpoint: {endpoint['EndpointName']}") + except ClientError as e: + print(f" ❌ Failed to delete Glue dev endpoint {endpoint['EndpointName']}: {e}") + + def _delete_sagemaker_notebooks(self, notebooks: List[Dict[str, Any]]) -> None: + """Delete SageMaker notebook instances.""" + for notebook in notebooks: + try: + # Stop the notebook if it's running + if notebook['NotebookInstanceStatus'] == 'InService': + self.sagemaker.stop_notebook_instance(NotebookInstanceName=notebook['NotebookInstanceName']) + print(f" ⏸️ Stopping SageMaker notebook: {notebook['NotebookInstanceName']}") + print(f" ⏳ Note: Notebook must be stopped before deletion") + + # If already stopped, delete it + elif notebook['NotebookInstanceStatus'] == 'Stopped': + self.sagemaker.delete_notebook_instance(NotebookInstanceName=notebook['NotebookInstanceName']) + print(f" βœ… Deleted SageMaker notebook: {notebook['NotebookInstanceName']}") + else: + print(f" ⏳ SageMaker notebook {notebook['NotebookInstanceName']} is in {notebook['NotebookInstanceStatus']} state") + except ClientError as e: + print(f" ❌ Failed to delete SageMaker notebook {notebook['NotebookInstanceName']}: {e}") + +def main(): + parser = argparse.ArgumentParser(description='Clean up IAM Vulnerable resources') + parser.add_argument('--profile', help='AWS profile to use') + parser.add_argument('--dry-run', action='store_true', help='Show what would be deleted without actually deleting') + parser.add_argument('--yes', action='store_true', help='Skip confirmation prompt') + + args = parser.parse_args() + + print("🧹 IAM Vulnerable Cleanup Script") + print("=" * 40) + + # Initialize cleanup + cleanup = IAMVulnerableCleanup(profile_name=args.profile, dry_run=args.dry_run) + + # Get account information + account_info = cleanup.get_account_info() + print(f"🏒 AWS Account: {account_info['account_id']}") + print(f"πŸ‘€ Current Identity: {account_info['arn']}") + + # Find resources + resources = cleanup.find_iam_vulnerable_resources() + total_count = cleanup.display_resources(resources) + + if total_count == 0: + print("\nβœ… No IAM Vulnerable resources found!") + return + + # Confirmation + if not args.yes and not args.dry_run: + print(f"\n⚠️ WARNING: This will delete {total_count} resources!") + print("This action cannot be undone.") + response = input("\nDo you want to continue? (type 'yes' to confirm): ") + if response.lower() != 'yes': + print("❌ Operation cancelled.") + return + + # Delete resources + cleanup.delete_resources(resources) + + if args.dry_run: + print(f"\nπŸ” DRY RUN COMPLETE: Found {total_count} resources that would be deleted") + else: + print(f"\nβœ… CLEANUP COMPLETE: Processed {total_count} resources") + print("Note: Some resources (like CloudFormation stacks) may take additional time to fully delete.") + +if __name__ == '__main__': + main() diff --git a/cleanup-scripts/cleanup_iam_vulnerable.sh b/cleanup-scripts/cleanup_iam_vulnerable.sh new file mode 100755 index 0000000..e277e16 --- /dev/null +++ b/cleanup-scripts/cleanup_iam_vulnerable.sh @@ -0,0 +1,856 @@ +#!/bin/bash + +# IAM Vulnerable Cleanup Script (Bash Version) +# +# This script cleans up all resources created by the iam-vulnerable Terraform project. +# It's designed for cases where the Terraform state is lost and 'terraform destroy' cannot be used. +# +# Usage: +# ./cleanup_iam_vulnerable.sh [--profile PROFILE_NAME] [--dry-run] [--yes] +# +# Requirements: +# - AWS CLI v2 (configured with appropriate credentials) +# - jq (for JSON parsing) + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Global variables +AWS_PROFILE="" +DRY_RUN=false +SKIP_CONFIRMATION=false +ACCOUNT_ID="" +TOTAL_RESOURCES=0 + +# Resource arrays +declare -a USERS=() +declare -a ROLES=() +declare -a GROUPS=() +declare -a POLICIES=() +declare -a ACCESS_KEYS=() +declare -a INSTANCE_PROFILES=() +declare -a LAMBDA_FUNCTIONS=() +declare -a CLOUDFORMATION_STACKS=() +declare -a GLUE_DEV_ENDPOINTS=() +declare -a SAGEMAKER_NOTEBOOKS=() + +# Function to print colored output +print_color() { + local color=$1 + local message=$2 + echo -e "${color}${message}${NC}" +} + +# Function to print usage +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --profile PROFILE AWS profile to use" + echo " --dry-run Show what would be deleted without actually deleting" + echo " --yes Skip confirmation prompt" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 --profile my-aws-profile --dry-run" + echo " $0 --yes" +} + +# Function to check dependencies +check_dependencies() { + if ! command -v aws &> /dev/null; then + print_color "$RED" "❌ Error: AWS CLI not found. Please install AWS CLI v2." + exit 1 + fi + + if ! command -v jq &> /dev/null; then + print_color "$RED" "❌ Error: jq not found. Please install jq for JSON parsing." + exit 1 + fi +} + +# Function to set AWS CLI options +set_aws_options() { + if [[ -n "$AWS_PROFILE" ]]; then + AWS_OPTS="--profile $AWS_PROFILE" + else + AWS_OPTS="" + fi +} + +# Function to get account information +get_account_info() { + print_color "$BLUE" "πŸ” Getting AWS account information..." + + local identity + if ! identity=$(aws sts get-caller-identity $AWS_OPTS 2>/dev/null); then + print_color "$RED" "❌ Error: Unable to get AWS account information. Please check your credentials." + exit 1 + fi + + ACCOUNT_ID=$(echo "$identity" | jq -r '.Account') + local user_id=$(echo "$identity" | jq -r '.UserId') + local arn=$(echo "$identity" | jq -r '.Arn') + + print_color "$CYAN" "🏒 AWS Account: $ACCOUNT_ID" + print_color "$CYAN" "πŸ‘€ Current Identity: $arn" +} + +# Function to check if a name matches IAM vulnerable patterns +matches_iam_vulnerable_pattern() { + local name=$1 + local patterns=("privesc" "fn1-" "fn2-" "fn3-" "fn4-" "fp1-" "fp2-" "fp3-" "fp4-" "fp5-" "Ryan") + + for pattern in "${patterns[@]}"; do + if [[ "$name" == "$pattern"* ]]; then + return 0 + fi + done + return 1 +} + +# Function to find IAM users +find_iam_users() { + print_color "$BLUE" "πŸ” Scanning for IAM users..." + + local users_json + if users_json=$(aws iam list-users $AWS_OPTS --output json 2>/dev/null); then + while IFS= read -r user; do + local user_name=$(echo "$user" | jq -r '.UserName') + if matches_iam_vulnerable_pattern "$user_name"; then + USERS+=("$user_name") + ((TOTAL_RESOURCES++)) + + # Find access keys for this user + local keys_json + if keys_json=$(aws iam list-access-keys $AWS_OPTS --user-name "$user_name" --output json 2>/dev/null); then + while IFS= read -r key; do + local access_key_id=$(echo "$key" | jq -r '.AccessKeyId') + local status=$(echo "$key" | jq -r '.Status') + ACCESS_KEYS+=("$access_key_id:$user_name:$status") + ((TOTAL_RESOURCES++)) + done < <(echo "$keys_json" | jq -c '.AccessKeyMetadata[]?') + fi + fi + done < <(echo "$users_json" | jq -c '.Users[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list IAM users" + fi +} + +# Function to find IAM roles +find_iam_roles() { + print_color "$BLUE" "πŸ” Scanning for IAM roles..." + + local roles_json + if roles_json=$(aws iam list-roles $AWS_OPTS --output json 2>/dev/null); then + while IFS= read -r role; do + local role_name=$(echo "$role" | jq -r '.RoleName') + if matches_iam_vulnerable_pattern "$role_name"; then + ROLES+=("$role_name") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$roles_json" | jq -c '.Roles[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list IAM roles" + fi +} + +# Function to find IAM groups +find_iam_groups() { + print_color "$BLUE" "πŸ” Scanning for IAM groups..." + + local groups_json + if groups_json=$(aws iam list-groups $AWS_OPTS --output json 2>/dev/null); then + while IFS= read -r group; do + local group_name=$(echo "$group" | jq -r '.GroupName') + if matches_iam_vulnerable_pattern "$group_name"; then + GROUPS+=("$group_name") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$groups_json" | jq -c '.Groups[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list IAM groups" + fi +} + +# Function to find IAM policies +find_iam_policies() { + print_color "$BLUE" "πŸ” Scanning for IAM policies..." + + local policies_json + if policies_json=$(aws iam list-policies $AWS_OPTS --scope Local --output json 2>/dev/null); then + while IFS= read -r policy; do + local policy_name=$(echo "$policy" | jq -r '.PolicyName') + local policy_arn=$(echo "$policy" | jq -r '.Arn') + if matches_iam_vulnerable_pattern "$policy_name"; then + POLICIES+=("$policy_name:$policy_arn") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$policies_json" | jq -c '.Policies[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list IAM policies" + fi +} + +# Function to find instance profiles +find_instance_profiles() { + print_color "$BLUE" "πŸ” Scanning for instance profiles..." + + local profiles_json + if profiles_json=$(aws iam list-instance-profiles $AWS_OPTS --output json 2>/dev/null); then + while IFS= read -r profile; do + local profile_name=$(echo "$profile" | jq -r '.InstanceProfileName') + if matches_iam_vulnerable_pattern "$profile_name"; then + INSTANCE_PROFILES+=("$profile_name") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$profiles_json" | jq -c '.InstanceProfiles[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list instance profiles" + fi +} + +# Function to find Lambda functions +find_lambda_functions() { + print_color "$BLUE" "πŸ” Scanning for Lambda functions..." + + local functions_json + if functions_json=$(aws lambda list-functions $AWS_OPTS --output json 2>/dev/null); then + while IFS= read -r function; do + local function_name=$(echo "$function" | jq -r '.FunctionName') + if [[ "$function_name" == "test_lambda" ]] || matches_iam_vulnerable_pattern "$function_name"; then + LAMBDA_FUNCTIONS+=("$function_name") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$functions_json" | jq -c '.Functions[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list Lambda functions" + fi +} + +# Function to find CloudFormation stacks +find_cloudformation_stacks() { + print_color "$BLUE" "πŸ” Scanning for CloudFormation stacks..." + + local stacks_json + if stacks_json=$(aws cloudformation list-stacks $AWS_OPTS --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE UPDATE_ROLLBACK_COMPLETE --output json 2>/dev/null); then + while IFS= read -r stack; do + local stack_name=$(echo "$stack" | jq -r '.StackName') + if [[ "$stack_name" == "privesc-cloudformationStack" ]] || matches_iam_vulnerable_pattern "$stack_name"; then + CLOUDFORMATION_STACKS+=("$stack_name") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$stacks_json" | jq -c '.StackSummaries[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list CloudFormation stacks" + fi +} + +# Function to find Glue dev endpoints +find_glue_dev_endpoints() { + print_color "$BLUE" "πŸ” Scanning for Glue dev endpoints..." + + local endpoints_json + if endpoints_json=$(aws glue get-dev-endpoints $AWS_OPTS --output json 2>/dev/null); then + while IFS= read -r endpoint; do + local endpoint_name=$(echo "$endpoint" | jq -r '.EndpointName') + if [[ "$endpoint_name" == "privesc-glue-devendpoint" ]] || matches_iam_vulnerable_pattern "$endpoint_name"; then + GLUE_DEV_ENDPOINTS+=("$endpoint_name") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$endpoints_json" | jq -c '.DevEndpoints[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list Glue dev endpoints" + fi +} + +# Function to find SageMaker notebook instances +find_sagemaker_notebooks() { + print_color "$BLUE" "πŸ” Scanning for SageMaker notebook instances..." + + local notebooks_json + if notebooks_json=$(aws sagemaker list-notebook-instances $AWS_OPTS --output json 2>/dev/null); then + while IFS= read -r notebook; do + local notebook_name=$(echo "$notebook" | jq -r '.NotebookInstanceName') + if [[ "$notebook_name" == "privesc-sagemakerNotebook" ]] || matches_iam_vulnerable_pattern "$notebook_name"; then + local status=$(echo "$notebook" | jq -r '.NotebookInstanceStatus') + SAGEMAKER_NOTEBOOKS+=("$notebook_name:$status") + ((TOTAL_RESOURCES++)) + fi + done < <(echo "$notebooks_json" | jq -c '.NotebookInstances[]?') + else + print_color "$YELLOW" "⚠️ Warning: Could not list SageMaker notebook instances" + fi +} + +# Function to display all found resources +display_resources() { + echo "" + print_color "$CYAN" "$(printf '=%.0s' {1..60})" + print_color "$CYAN" "πŸ“‹ IAM VULNERABLE RESOURCES FOUND" + print_color "$CYAN" "$(printf '=%.0s' {1..60})" + + if [[ ${#ACCESS_KEYS[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ ACCESS KEYS (${#ACCESS_KEYS[@]}):" + for key in "${ACCESS_KEYS[@]}"; do + IFS=':' read -r access_key_id user_name status <<< "$key" + echo " β€’ $access_key_id (User: $user_name, Status: $status)" + done + fi + + if [[ ${#USERS[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ USERS (${#USERS[@]}):" + for user in "${USERS[@]}"; do + echo " β€’ $user" + done + fi + + if [[ ${#GROUPS[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ GROUPS (${#GROUPS[@]}):" + for group in "${GROUPS[@]}"; do + echo " β€’ $group" + done + fi + + if [[ ${#ROLES[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ ROLES (${#ROLES[@]}):" + for role in "${ROLES[@]}"; do + echo " β€’ $role" + done + fi + + if [[ ${#POLICIES[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ POLICIES (${#POLICIES[@]}):" + for policy in "${POLICIES[@]}"; do + IFS=':' read -r policy_name policy_arn <<< "$policy" + echo " β€’ $policy_name (ARN: $policy_arn)" + done + fi + + if [[ ${#INSTANCE_PROFILES[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ INSTANCE PROFILES (${#INSTANCE_PROFILES[@]}):" + for profile in "${INSTANCE_PROFILES[@]}"; do + echo " β€’ $profile" + done + fi + + if [[ ${#LAMBDA_FUNCTIONS[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ LAMBDA FUNCTIONS (${#LAMBDA_FUNCTIONS[@]}):" + for function in "${LAMBDA_FUNCTIONS[@]}"; do + echo " β€’ $function" + done + fi + + if [[ ${#CLOUDFORMATION_STACKS[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ CLOUDFORMATION STACKS (${#CLOUDFORMATION_STACKS[@]}):" + for stack in "${CLOUDFORMATION_STACKS[@]}"; do + echo " β€’ $stack" + done + fi + + if [[ ${#GLUE_DEV_ENDPOINTS[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ GLUE DEV ENDPOINTS (${#GLUE_DEV_ENDPOINTS[@]}):" + for endpoint in "${GLUE_DEV_ENDPOINTS[@]}"; do + echo " β€’ $endpoint" + done + fi + + if [[ ${#SAGEMAKER_NOTEBOOKS[@]} -gt 0 ]]; then + echo "" + print_color "$BLUE" "πŸ”Έ SAGEMAKER NOTEBOOKS (${#SAGEMAKER_NOTEBOOKS[@]}):" + for notebook in "${SAGEMAKER_NOTEBOOKS[@]}"; do + IFS=':' read -r notebook_name status <<< "$notebook" + echo " β€’ $notebook_name (Status: $status)" + done + fi + + echo "" + print_color "$CYAN" "πŸ“Š TOTAL RESOURCES: $TOTAL_RESOURCES" +} + +# Function to delete access keys +delete_access_keys() { + if [[ ${#ACCESS_KEYS[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting access keys..." + + for key in "${ACCESS_KEYS[@]}"; do + IFS=':' read -r access_key_id user_name status <<< "$key" + + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete access key: $access_key_id (User: $user_name)" + else + if aws iam delete-access-key $AWS_OPTS --user-name "$user_name" --access-key-id "$access_key_id" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted access key: $access_key_id (User: $user_name)" + else + print_color "$RED" " ❌ Failed to delete access key: $access_key_id" + fi + fi + done +} + +# Function to delete users +delete_users() { + if [[ ${#USERS[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting users..." + + for user in "${USERS[@]}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete user: $user" + continue + fi + + # Detach user policies + local attached_policies + if attached_policies=$(aws iam list-attached-user-policies $AWS_OPTS --user-name "$user" --output json 2>/dev/null); then + while IFS= read -r policy; do + local policy_arn=$(echo "$policy" | jq -r '.PolicyArn') + local policy_name=$(echo "$policy" | jq -r '.PolicyName') + if aws iam detach-user-policy $AWS_OPTS --user-name "$user" --policy-arn "$policy_arn" 2>/dev/null; then + echo " πŸ”— Detached policy $policy_name from user $user" + fi + done < <(echo "$attached_policies" | jq -c '.AttachedPolicies[]?') + fi + + # Delete inline user policies + local inline_policies + if inline_policies=$(aws iam list-user-policies $AWS_OPTS --user-name "$user" --output json 2>/dev/null); then + while IFS= read -r policy_name; do + if aws iam delete-user-policy $AWS_OPTS --user-name "$user" --policy-name "$policy_name" 2>/dev/null; then + echo " πŸ”— Deleted inline policy $policy_name from user $user" + fi + done < <(echo "$inline_policies" | jq -r '.PolicyNames[]?') + fi + + # Remove user from groups + local groups + if groups=$(aws iam get-groups-for-user $AWS_OPTS --user-name "$user" --output json 2>/dev/null); then + while IFS= read -r group; do + local group_name=$(echo "$group" | jq -r '.GroupName') + if aws iam remove-user-from-group $AWS_OPTS --user-name "$user" --group-name "$group_name" 2>/dev/null; then + echo " πŸ‘₯ Removed user $user from group $group_name" + fi + done < <(echo "$groups" | jq -c '.Groups[]?') + fi + + # Delete login profile if exists + if aws iam delete-login-profile $AWS_OPTS --user-name "$user" 2>/dev/null; then + echo " πŸ”‘ Deleted login profile for user $user" + fi + + # Delete the user + if aws iam delete-user $AWS_OPTS --user-name "$user" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted user: $user" + else + print_color "$RED" " ❌ Failed to delete user: $user" + fi + done +} + +# Function to delete groups +delete_groups() { + if [[ ${#GROUPS[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting groups..." + + for group in "${GROUPS[@]}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete group: $group" + continue + fi + + # Detach group policies + local attached_policies + if attached_policies=$(aws iam list-attached-group-policies $AWS_OPTS --group-name "$group" --output json 2>/dev/null); then + while IFS= read -r policy; do + local policy_arn=$(echo "$policy" | jq -r '.PolicyArn') + local policy_name=$(echo "$policy" | jq -r '.PolicyName') + if aws iam detach-group-policy $AWS_OPTS --group-name "$group" --policy-arn "$policy_arn" 2>/dev/null; then + echo " πŸ”— Detached policy $policy_name from group $group" + fi + done < <(echo "$attached_policies" | jq -c '.AttachedPolicies[]?') + fi + + # Delete inline group policies + local inline_policies + if inline_policies=$(aws iam list-group-policies $AWS_OPTS --group-name "$group" --output json 2>/dev/null); then + while IFS= read -r policy_name; do + if aws iam delete-group-policy $AWS_OPTS --group-name "$group" --policy-name "$policy_name" 2>/dev/null; then + echo " πŸ”— Deleted inline policy $policy_name from group $group" + fi + done < <(echo "$inline_policies" | jq -r '.PolicyNames[]?') + fi + + # Delete the group + if aws iam delete-group $AWS_OPTS --group-name "$group" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted group: $group" + else + print_color "$RED" " ❌ Failed to delete group: $group" + fi + done +} + +# Function to delete roles +delete_roles() { + if [[ ${#ROLES[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting roles..." + + for role in "${ROLES[@]}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete role: $role" + continue + fi + + # Detach role policies + local attached_policies + if attached_policies=$(aws iam list-attached-role-policies $AWS_OPTS --role-name "$role" --output json 2>/dev/null); then + while IFS= read -r policy; do + local policy_arn=$(echo "$policy" | jq -r '.PolicyArn') + local policy_name=$(echo "$policy" | jq -r '.PolicyName') + if aws iam detach-role-policy $AWS_OPTS --role-name "$role" --policy-arn "$policy_arn" 2>/dev/null; then + echo " πŸ”— Detached policy $policy_name from role $role" + fi + done < <(echo "$attached_policies" | jq -c '.AttachedPolicies[]?') + fi + + # Delete inline role policies + local inline_policies + if inline_policies=$(aws iam list-role-policies $AWS_OPTS --role-name "$role" --output json 2>/dev/null); then + while IFS= read -r policy_name; do + if aws iam delete-role-policy $AWS_OPTS --role-name "$role" --policy-name "$policy_name" 2>/dev/null; then + echo " πŸ”— Deleted inline policy $policy_name from role $role" + fi + done < <(echo "$inline_policies" | jq -r '.PolicyNames[]?') + fi + + # Delete the role + if aws iam delete-role $AWS_OPTS --role-name "$role" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted role: $role" + else + print_color "$RED" " ❌ Failed to delete role: $role" + fi + done +} + +# Function to delete policies +delete_policies() { + if [[ ${#POLICIES[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting policies..." + + for policy in "${POLICIES[@]}"; do + IFS=':' read -r policy_name policy_arn <<< "$policy" + + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete policy: $policy_name" + continue + fi + + # Delete all policy versions except the default + local versions + if versions=$(aws iam list-policy-versions $AWS_OPTS --policy-arn "$policy_arn" --output json 2>/dev/null); then + while IFS= read -r version; do + local version_id=$(echo "$version" | jq -r '.VersionId') + local is_default=$(echo "$version" | jq -r '.IsDefaultVersion') + if [[ "$is_default" != "true" ]]; then + if aws iam delete-policy-version $AWS_OPTS --policy-arn "$policy_arn" --version-id "$version_id" 2>/dev/null; then + echo " πŸ“„ Deleted policy version $version_id for $policy_name" + fi + fi + done < <(echo "$versions" | jq -c '.Versions[]?') + fi + + # Delete the policy + if aws iam delete-policy $AWS_OPTS --policy-arn "$policy_arn" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted policy: $policy_name" + else + print_color "$RED" " ❌ Failed to delete policy: $policy_name" + fi + done +} + +# Function to delete instance profiles +delete_instance_profiles() { + if [[ ${#INSTANCE_PROFILES[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting instance profiles..." + + for profile in "${INSTANCE_PROFILES[@]}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete instance profile: $profile" + continue + fi + + # Remove roles from instance profile + local profile_info + if profile_info=$(aws iam get-instance-profile $AWS_OPTS --instance-profile-name "$profile" --output json 2>/dev/null); then + while IFS= read -r role; do + local role_name=$(echo "$role" | jq -r '.RoleName') + if aws iam remove-role-from-instance-profile $AWS_OPTS --instance-profile-name "$profile" --role-name "$role_name" 2>/dev/null; then + echo " πŸ”— Removed role $role_name from instance profile $profile" + fi + done < <(echo "$profile_info" | jq -c '.InstanceProfile.Roles[]?') + fi + + # Delete the instance profile + if aws iam delete-instance-profile $AWS_OPTS --instance-profile-name "$profile" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted instance profile: $profile" + else + print_color "$RED" " ❌ Failed to delete instance profile: $profile" + fi + done +} + +# Function to delete Lambda functions +delete_lambda_functions() { + if [[ ${#LAMBDA_FUNCTIONS[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting Lambda functions..." + + for function in "${LAMBDA_FUNCTIONS[@]}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete Lambda function: $function" + else + if aws lambda delete-function $AWS_OPTS --function-name "$function" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted Lambda function: $function" + else + print_color "$RED" " ❌ Failed to delete Lambda function: $function" + fi + fi + done +} + +# Function to delete CloudFormation stacks +delete_cloudformation_stacks() { + if [[ ${#CLOUDFORMATION_STACKS[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting CloudFormation stacks..." + + for stack in "${CLOUDFORMATION_STACKS[@]}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete CloudFormation stack: $stack" + else + if aws cloudformation delete-stack $AWS_OPTS --stack-name "$stack" 2>/dev/null; then + print_color "$GREEN" " βœ… Initiated deletion of CloudFormation stack: $stack" + print_color "$YELLOW" " ⏳ Note: Stack deletion may take several minutes to complete" + else + print_color "$RED" " ❌ Failed to delete CloudFormation stack: $stack" + fi + fi + done +} + +# Function to delete Glue dev endpoints +delete_glue_dev_endpoints() { + if [[ ${#GLUE_DEV_ENDPOINTS[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting Glue dev endpoints..." + + for endpoint in "${GLUE_DEV_ENDPOINTS[@]}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete Glue dev endpoint: $endpoint" + else + if aws glue delete-dev-endpoint $AWS_OPTS --endpoint-name "$endpoint" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted Glue dev endpoint: $endpoint" + else + print_color "$RED" " ❌ Failed to delete Glue dev endpoint: $endpoint" + fi + fi + done +} + +# Function to delete SageMaker notebook instances +delete_sagemaker_notebooks() { + if [[ ${#SAGEMAKER_NOTEBOOKS[@]} -eq 0 ]]; then + return + fi + + echo "" + print_color "$BLUE" "πŸ”Έ Deleting SageMaker notebook instances..." + + for notebook in "${SAGEMAKER_NOTEBOOKS[@]}"; do + IFS=':' read -r notebook_name status <<< "$notebook" + + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY RUN] Would delete SageMaker notebook: $notebook_name" + continue + fi + + # Stop the notebook if it's running + if [[ "$status" == "InService" ]]; then + if aws sagemaker stop-notebook-instance $AWS_OPTS --notebook-instance-name "$notebook_name" 2>/dev/null; then + print_color "$YELLOW" " ⏸️ Stopping SageMaker notebook: $notebook_name" + print_color "$YELLOW" " ⏳ Note: Notebook must be stopped before deletion" + fi + elif [[ "$status" == "Stopped" ]]; then + if aws sagemaker delete-notebook-instance $AWS_OPTS --notebook-instance-name "$notebook_name" 2>/dev/null; then + print_color "$GREEN" " βœ… Deleted SageMaker notebook: $notebook_name" + else + print_color "$RED" " ❌ Failed to delete SageMaker notebook: $notebook_name" + fi + else + print_color "$YELLOW" " ⏳ SageMaker notebook $notebook_name is in $status state" + fi + done +} + +# Function to delete all resources +delete_resources() { + if [[ "$DRY_RUN" == true ]]; then + print_color "$BLUE" "\nπŸ” DRY RUN MODE - No resources will be deleted" + return + fi + + print_color "$BLUE" "\nπŸ—‘οΈ Starting resource deletion..." + + # Deletion order is important to avoid dependency conflicts + delete_access_keys + delete_sagemaker_notebooks + delete_glue_dev_endpoints + delete_lambda_functions + delete_cloudformation_stacks + delete_instance_profiles + delete_users + delete_groups + delete_roles + delete_policies +} + +# Function to find all resources +find_all_resources() { + find_iam_users + find_iam_roles + find_iam_groups + find_iam_policies + find_instance_profiles + find_lambda_functions + find_cloudformation_stacks + find_glue_dev_endpoints + find_sagemaker_notebooks +} + +# Main function +main() { + # Parse command line arguments + while [[ $# -gt 0 ]]; do + case $1 in + --profile) + AWS_PROFILE="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --yes) + SKIP_CONFIRMATION=true + shift + ;; + --help) + usage + exit 0 + ;; + *) + print_color "$RED" "Unknown option: $1" + usage + exit 1 + ;; + esac + done + + print_color "$CYAN" "🧹 IAM Vulnerable Cleanup Script" + print_color "$CYAN" "$(printf '=%.0s' {1..40})" + + # Check dependencies + check_dependencies + + # Set AWS options + set_aws_options + + # Get account information + get_account_info + + # Find all resources + find_all_resources + + # Display resources + display_resources + + if [[ $TOTAL_RESOURCES -eq 0 ]]; then + print_color "$GREEN" "\nβœ… No IAM Vulnerable resources found!" + exit 0 + fi + + # Confirmation + if [[ "$SKIP_CONFIRMATION" == false ]] && [[ "$DRY_RUN" == false ]]; then + echo "" + print_color "$YELLOW" "⚠️ WARNING: This will delete $TOTAL_RESOURCES resources!" + print_color "$YELLOW" "This action cannot be undone." + echo "" + read -p "Do you want to continue? (type 'yes' to confirm): " response + if [[ "$response" != "yes" ]]; then + print_color "$RED" "❌ Operation cancelled." + exit 0 + fi + fi + + # Delete resources + delete_resources + + # Final message + if [[ "$DRY_RUN" == true ]]; then + print_color "$BLUE" "\nπŸ” DRY RUN COMPLETE: Found $TOTAL_RESOURCES resources that would be deleted" + else + print_color "$GREEN" "\nβœ… CLEANUP COMPLETE: Processed $TOTAL_RESOURCES resources" + print_color "$YELLOW" "Note: Some resources (like CloudFormation stacks) may take additional time to fully delete." + fi +} + +# Run main function +main "$@" diff --git a/main.tf b/main.tf index 0e27f44..3ee9f04 100644 --- a/main.tf +++ b/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.9" + version = "~> 6.0" } } } @@ -32,10 +32,10 @@ module "tool-testing" { # Uncomment the next module to create a lambda and related resources ################### -#module "lambda" { +# module "lambda" { # source = "./modules/non-free-resources/lambda" # aws_assume_role_arn = (var.aws_assume_role_arn != "" ? var.aws_assume_role_arn : data.aws_caller_identity.current.arn) -#} +# } ################### # Module: EC2 diff --git a/modules/non-free-resources/lambda/lambda.tf b/modules/non-free-resources/lambda/lambda.tf index 5e32b56..bef3289 100644 --- a/modules/non-free-resources/lambda/lambda.tf +++ b/modules/non-free-resources/lambda/lambda.tf @@ -33,7 +33,7 @@ resource "aws_lambda_function" "test_lambda" { role = aws_iam_role.privesc-high-priv-lambda-role2.arn handler = "index.handler" source_code_hash = data.archive_file.lambda_zip.output_base64sha256 - runtime = "nodejs18.x" + runtime = "nodejs20.x" } resource "aws_iam_role" "privesc-high-priv-lambda-role2" { name = "privesc-high-priv-lambda-role2" diff --git a/modules/non-free-resources/lambda/lambda_function.zip b/modules/non-free-resources/lambda/lambda_function.zip index c6f827945921429ca913e8d5b51faa64916a6a56..47ae9ac8cf091fc979be5c7141b6f2ba824c049d 100644 GIT binary patch delta 173 zcmeys_>OUcZhh;+d%-=SLLAkN38{~H*BpLxnd7j{&AC?_D#|?tPs{ID=O`L zo09rY)9V!%s`orF?7GC9&GX5!U7=06N=4B>(^b delta 178 zcmaFI_vo1YGT->~1t-Sc+V zq7x;{s!jWr-|jHiNlV&(Ku)-}pIs>{HfG`#`NsvVtIn4MuKyI-y#2C)?%l2WQ%~`D zY&SozHTUnddIkoD|NjHL**Q)s`b6gec}+kZ;LXS+!Ysm|$ict?