Skip to main content

AWS Credentials Manager - Pull temporary credentials for an IAM user with optional SSH-key encryption

Project description

iam-sorry

PyPI Downloads License Python Version Build Status

A powerful CLI utility for delegated management of temporary AWS credentials and namespaces. This tool empowers teams by separating responsibilities and simplifying access workflows.

  • For AWS Administrators:

    • Secure Delegation: Set up a single management IAM user with self rotating, encrypted, permanent credentials for each of your power users . This user acts as a broker, eliminating the need to manage credentials for every end-user directly.
    • Enforce Isolation: Safely partition a single AWS account into "namespaces," ensuring that teams or individuals can only operate within their designated area.
    • Clear separation between authentication (self-service, roles and role policy creation) and autorization (Admin still needs to apply policies giving access to AWS services)
  • For End-Users (Research Software Engineers, Systems Administrators):

    • Self-Service Access: Refresh your temporary AWS credentials with a single, simple command.
    • Namespace Autonomy: Gain the freedom to manage all necessary resources within your assigned namespace without waiting for an administrator.
    • Simple Interface: Get the access you need through a straightforward CLI, without needing deep expertise in AWS IAM policies.

Key Features:

  • Auto-Bootstrap: Zero-config setup from environment variables (permanent or temporary)
  • Temporary Credentials: Generate AWS credentials with up to 36 hours validity
  • SSH-Key Encryption: AES-256-GCM encryption for manager profiles using password-protected SSH keys
  • Automatic Rotation: Admin-provided credentials automatically rotated during bootstrap
  • Role Management: Create IAM roles with automatic base user setup and trust policies
  • Namespace Isolation: Prefix-based access control prevents cross-namespace user management
  • User Delegation: Transfer user ownership with permanent tag-based tracking (--chown)
  • Automatic Refresh: Expired credentials auto-refresh during --eval operations
  • Policy Generation: Print admin or refresh-only policies for any namespace prefix
  • Profile Verification: Check and fix AWS config files (--fix-profiles)
  • Lambda Policies: Generate deployment policies with namespace-based S3 and DynamoDB access
  • Python API: Use as library with create_session_with_profile() for boto3 integration
  • Lazy Decryption: Credentials stay encrypted on disk, decrypted only when needed

Quick Start

This example shows how an AWS Administrator provisions a new manager account sue-mgr using their AWS profile aws-admin, and how end user sue completes the setup.

AWS Administrator

Create the manager user with a single command:

iam-sorry --profile aws-admin --create-iam-sorry sue-mgr

This creates IAM user sue-mgr, attaches the namespace management policy, and generates AWS credentials.

End User Setup

Sue receives an email from the Administrator with:

  1. Instructions for setting up agent-managed SSH keys
  2. Installation command: pip install iam-sorry
  3. Standard AWS credentials

Sue exports the credentials and runs iam-sorry for auto-bootstrap:

export AWS_ACCESS_KEY_ID='AKIAXXXXXXXXXXXXXXXXXXXX'
export AWS_SECRET_ACCESS_KEY='MU/KBXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
export AWS_REGION='us-west-2'  # Change if needed
iam-sorry

What happens during bootstrap:

  • Sets up AWS profile [sue-mgr] with encrypted credentials
  • Automatically rotates the admin-provided credentials (creates permanent key, disables old one)
  • Creates base IAM user sue with AWS profile [sue]
  • Generates 36-hour temporary credentials for sue

Daily Usage

Sue can now refresh credentials with a single command:

iam-sorry sue

This works as long as she's logged into her shell with her SSH key loaded in ssh-agent.

Shell Scripts

For automated scripts, Sue can ensure AWS credentials are available as environment variables with automatic refresh if expired:

eval $(iam-sorry --eval sue)
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN now set
# Your AWS commands here...
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

Administrator: Assign Permissions

The AWS Administrator can now assign policies to user sue or to roles that Sue creates using iam-sorry:

# Assign permissions to Sue's base user
aws iam attach-user-policy --user-name sue --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

# Or to a role Sue created
aws iam put-role-policy --role-name iam-sorry-sue-lambda --policy-name lambda-deploy --policy-document file://policy.json

Vision & Architecture

Use Case Coverage

This tool is pragmatically designed to handle three distinct credential management scenarios:

  1. Broad Temporary Access (most common)

    • Short-lived credentials for batch operations
    • Limited to specific duration (1-36 hours)
    • Auto-expiration provides security boundary
    • Ideal for: HPC jobs, data processing, CI/CD pipelines
  2. Narrow Permanent Access (low-risk services)

    • Long-lived credentials for specific services
    • Limited permissions per service role
    • Examples: bedrock, analytics, logging services
    • Ideal for: Service-to-service authentication
  3. Powerful IAM Access (critical, encrypted)

    • Full IAM management capabilities
    • High privilege, sensitive credentials
    • Protected with SSH-key encryption
    • Ideal for: Infrastructure automation, user provisioning

Current Implementation: SSH-Key Based (Pragmatic)

This interim solution leverages existing security infrastructure already in place at most organizations:

Why SSH Keys?

  • Most HPC users, AI researchers, and developers already have password-protected SSH keys
  • SSH keys commonly used for GitHub, GitLab, and other critical services
  • SSH-agent caching eliminates repeated passphrase entry
  • No additional credential management required
  • Works in headless environments (HPC nodes, servers) without browser interaction

Pragmatic Benefits:

  • Zero additional setup for users with existing SSH infrastructure
  • Leverages familiar SSH passphrase protection
  • Compatible with existing ssh-agent workflows
  • No new password requirements
  • Works offline and in air-gapped environments

Future Vision: Department-Level Service (Long-Term)

The longer-term architectural vision is a centralized, auditable system:

┌───────────────────────────────────────────────────────────┐
│                                                           │
│  User Authentication                                      │
│  ├─ Active Directory / LDAP                               │
│  ├─ Kerberos (krb5)                                       │
│  └─ Headless authentication (no browser required)         │
│         ↓                                                 │
│  Department IAM Service                                   │
│  ├─ Service runs with high-privilege IAM role             │
│  ├─ Receives delegated requests from users                │
│  ├─ All actions logged with user identity                 │
│  ├─ Audit trail for compliance                            │
│  └─ Permission model based on user group                  │
│         ↓                                                 │
│  AWS IAM Actions                                          │
│  ├─ create-user, delete-user                              │
│  ├─ attach-policy, detach-policy                          │
│  ├─ create-access-key, rotate-key                         │
│  └─ All traceable to user who initiated action            │
│                                                           │
└───────────────────────────────────────────────────────────┘

Benefits:
  ✓ Single point of IAM access (easier to audit)
  ✓ All actions logged with user identity
  ✓ Centralized permission model
  ✓ No credential distribution needed
  ✓ Reduces credential loss risk
  ✓ Prevents unauthorized IAM abuse
  ✓ Department-wide credential management

Why This is Better for Production:

  • Single high-privilege credential (service identity) instead of many
  • All IAM actions attributed to specific users
  • Centralized audit trail for compliance
  • Fine-grained permission delegation
  • Kerberos authentication (works in headless environments)
  • No credentials stored on individual machines

Current vs. Future Tradeoffs

Aspect Current (SSH-Key) Future (Service)
Setup Per-user (self-service) Per-department (IT-managed)
Auth SSH passphrase Kerberos (krb5)
Credentials Distributed (each user) Centralized (service only)
Audit Trail Limited (per-machine logs) Complete (all via service)
Offline Access ✓ Yes ✗ No (requires network)
Headless Systems ✓ Excellent ✓ Excellent (Kerberos capable)
Security Model Good (encrypted at-rest) Excellent (centralized)
Compliance Medium (per-machine audit) High (centralized logging)

Why SSH-Key Approach Now?

The interim SSH-key solution is chosen because:

  1. Existing Infrastructure: Most shell users already have password-protected SSH keys
  2. Headless Compatibility: Works on HPC nodes, servers without browser
  3. No Additional Setup: Leverages existing ssh-agent workflows
  4. Quick Deployment: Can be deployed immediately to self-sufficient users
  5. Low Friction: Minimal learning curve for technical users
  6. Offline Capable: Works in air-gapped environments

This allows departments to:

  • Protect credentials immediately
  • Give users self-service temporary credential generation
  • Maintain audit logs at system level
  • Plan for centralized service later without user disruption

Target Users

This Tool is Best For:

  • Self-sufficient technical users (DevOps, HPC, AI researchers)
  • Organizations with existing SSH key infrastructure
  • Teams using headless systems (no browser)
  • Users needing on-demand temporary credentials
  • Environments without centralized SSO/AD integration

This Tool is NOT For:

  • Non-technical end users
  • Systems requiring browser-based authentication

Installation

1. Download

pip install iam-sorry

Or from source:

git clone https://github.com/dirkpetersen/iam-sorry
cd iam-sorry
pip install -e .

2. Prepare SSH Key

Your ED25519 SSH key must be password-protected:

# Check if your key is protected
ssh-keygen -l -f ~/.ssh/id_ed25519

# Add passphrase if not protected
ssh-keygen -p -f ~/.ssh/id_ed25519

3. Create iam-sorry Manager User

You have two options to create your iam-sorry manager user:

Option 1: Automated (Recommended) - If You Have IAM Admin Access

If you have a profile with full IAM admin permissions:

# Create the manager user with inline policy (one command!)
iam-sorry --profile iam-admin --create-iam-sorry dirk-iam-sorry

# This will:
# 1. Create IAM user 'dirk-iam-sorry'
# 2. Generate and attach inline policy 'iam-sorry-dirk'
# 3. Create access key
# 4. Display credentials in .aws/credentials format

Output:

======================================================================
CREDENTIALS (add to ~/.aws/credentials)
======================================================================

[iam-sorry]
aws_access_key_id = AKIA...
aws_secret_access_key = ...

======================================================================
IMPORTANT: Save these credentials now - they cannot be retrieved later!
======================================================================

Recommended username format: <prefix>-iam-sorry (e.g., dirk-iam-sorry, alice-iam-sorry)

Option 2: Manual - Request from Your AWS Administrator

If you don't have IAM admin access, request your administrator create the user:

# Generate the policy document
iam-sorry --print-policy dirk

# Send the output to your AWS administrator with these instructions:
# 1. Create IAM user 'dirk-iam-sorry' (or '<prefix>-iam-sorry')
# 2. Attach the policy as an inline policy named 'iam-sorry-dirk'
# 3. Create access key and provide credentials

Your administrator will provide you with:

  • AWS Access Key ID
  • AWS Secret Access Key

Add these to ~/.aws/credentials:

[iam-sorry]
aws_access_key_id = AKIA...
aws_secret_access_key = ...

Add these to ~/.aws/config:

[profile iam-sorry]
region = us-west-2

4. Encrypt iam-sorry Profile (Required)

# Encrypt your permanent manager credentials
iam-sorry --encrypt
# ✓ Manager profile 'iam-sorry' encrypted with SSH key

Getting Started Workflow

You have your iam-sorry manager user set up only once, here's the complete workflow:

Step 1: Initial Setup (One-Time)

Create your iam-sorry manager user (choose Option 1 or Option 2 above) and encrypt the profile.

Step 2: Create and Manage Users

Use the iam-sorry profile to create users in your namespace with temporary credentials:

# Create new users (auto-creates IAM user if doesn't exist)
iam-sorry dirk-admin
# ✓ IAM user 'dirk-admin' created
# ✓ Successfully updated profile 'dirk-admin'
# ✓ Credentials expire at: 2025-10-26T12:00:00

iam-sorry dirk-bedrock
# ✓ IAM user 'dirk-bedrock' created
# ✓ Credentials expire at: 2025-10-26T12:00:00

# Refresh credentials for existing profiles (before they expire)
iam-sorry dirk-admin
# ✓ Successfully updated profile 'dirk-admin'
# ✓ Credentials expire at: 2025-10-27T12:00:00

Important Notes:

  • New users created by iam-sorry: Have restrictive inline policy iam-sorry-<prefix> automatically attached, providing strong protection
  • Existing IAM users (created before iam-sorry): Can refresh their credentials, but they don't have the restrictive inline policy and are not as well protected from future permission changes

Step 3: Use Temporary Credentials

# Use the temporary credentials profile
export AWS_PROFILE=dirk-admin
aws s3 ls

# Or inject into environment for batch operations
eval $(iam-sorry --eval dirk-admin)
for bucket in bucket1 bucket2; do
  aws s3 sync ./data s3://$bucket/
done
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

Usage Guide

Encrypt Manager Profile

# One-time: Encrypt your powerful manager credentials
iam-sorry --profile usermanager --encrypt

# Verify encryption
iam-sorry --show-encrypted usermanager

Result:

[usermanager]
aws_access_key_id = __encrypted__:pdZLbbMlei28ZA0vhsT6gesOG6BLB...
aws_secret_access_key = __encrypted__:1vYfR8x2kjhsRR0vhsT6gesOG6B...

Generate Temporary Credentials

# Generate temporary credentials for a specific IAM user
iam-sorry --profile usermanager admin

# Specify duration (1-36 hours, default: 36)
iam-sorry --profile usermanager --duration 12 admin

# Using environment variable instead of --profile
AWS_PROFILE=usermanager iam-sorry admin

Output:

✓ Successfully updated profile 'admin'
✓ IAM user: admin
✓ Credentials expire at: 2025-10-26T12:00:00

Batch Operations with Encryption

# Inject encrypted manager credentials into environment
eval $(iam-sorry --eval usermanager)

# Now run batch IAM operations
for user in user1 user2 user3; do
  aws iam create-user --user-name $user
  aws iam attach-user-policy --user-name $user \
    --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
done

# Cleanup
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

Batch Operations with Permanent Credentials

# For low-risk permanent credentials (bedrock service)
eval $(iam-sorry --eval bedrock)

# Run bulk operations
aws bedrock list-foundation-models
aws bedrock create-evaluation-job --config file://config.json

# Cleanup
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

View Credentials

# View encrypted credentials (ciphertext, no decryption)
iam-sorry --show-encrypted usermanager

# View decrypted credentials (requires SSH passphrase)
iam-sorry --show-decrypted usermanager

# View plaintext credentials (for unencrypted profiles)
iam-sorry --show-decrypted bedrock

Python API Usage

You can use iam-sorry as a Python library in your code, providing the same functionality as the --eval option but programmatically.

Method 1: Direct boto3 Session (Recommended)

The simplest way - creates a boto3 session with auto-decrypted credentials:

from iamsorry import create_session_with_profile

# Create a session with auto-decrypted credentials
session = create_session_with_profile('dirk-admin')

# Use with any AWS service
s3 = session.client('s3')
buckets = s3.list_buckets()

ec2 = session.resource('ec2')
instances = ec2.instances.all()

# Works with encrypted profiles too!
session = create_session_with_profile('iam-sorry')
iam = session.client('iam')
users = iam.list_users()

Benefits:

  • ✅ Automatic credential decryption (SSH key required for encrypted profiles)
  • ✅ No environment variable pollution
  • ✅ Clean, Pythonic API
  • ✅ Session-scoped credentials

Method 2: Environment Variable Injection

Similar to eval $(iam-sorry --eval profile) but in Python:

from iamsorry import read_aws_credentials, get_aws_credentials_path
import os

# Read and auto-decrypt credentials
creds_file = get_aws_credentials_path()
config = read_aws_credentials(creds_file, auto_decrypt=True)

# Get profile credentials
profile = config['dirk-admin']

# Inject into environment
os.environ['AWS_ACCESS_KEY_ID'] = profile['aws_access_key_id']
os.environ['AWS_SECRET_ACCESS_KEY'] = profile['aws_secret_access_key']
if 'aws_session_token' in profile:
    os.environ['AWS_SESSION_TOKEN'] = profile['aws_session_token']

# Now use with default boto3 client (uses environment)
import boto3
s3 = boto3.client('s3')
s3.list_buckets()

# Cleanup
del os.environ['AWS_ACCESS_KEY_ID']
del os.environ['AWS_SECRET_ACCESS_KEY']
if 'AWS_SESSION_TOKEN' in os.environ:
    del os.environ['AWS_SESSION_TOKEN']

Benefits:

  • ✅ Compatible with libraries that read from environment
  • ✅ Similar to CLI --eval workflow
  • ⚠️ Requires manual cleanup

Method 3: Get Credentials as Dictionary

For cases where you need raw credential values:

from iamsorry import read_aws_credentials, get_aws_credentials_path

# Read credentials (auto-decrypt if encrypted)
creds_file = get_aws_credentials_path()
config = read_aws_credentials(creds_file, auto_decrypt=True)

# Get profile as dict
profile = config['dirk-admin']

# Access individual values
access_key = profile['aws_access_key_id']
secret_key = profile['aws_secret_access_key']
session_token = profile.get('aws_session_token', None)  # Optional
expiration = profile.get('expiration', None)  # Optional

# Use with custom AWS SDK wrappers or other tools
my_custom_aws_client(access_key, secret_key, session_token)

Example: Using in a Python Script

#!/usr/bin/env python3
"""
Backup S3 buckets using iam-sorry encrypted credentials.
"""

from iamsorry import create_session_with_profile
import sys

def backup_buckets(profile_name, destination):
    """Backup all S3 buckets to local destination."""
    try:
        # Create session with auto-decrypted credentials
        session = create_session_with_profile(profile_name)
        s3 = session.client('s3')

        # List all buckets
        response = s3.list_buckets()
        buckets = response['Buckets']

        print(f"Found {len(buckets)} buckets")

        for bucket in buckets:
            bucket_name = bucket['Name']
            print(f"Backing up: {bucket_name}")

            # Download bucket contents
            # ... your backup logic here ...

        print(f"✓ Backup complete: {len(buckets)} buckets")

    except Exception as e:
        print(f"Error: Failed to backup buckets: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    # Use encrypted iam-sorry profile credentials
    backup_buckets('iam-sorry', '/backup/s3')

Example: Using in a Long-Running Service

#!/usr/bin/env python3
"""
AWS monitoring service using iam-sorry credentials with auto-refresh.
"""

from iamsorry import (
    create_session_with_profile,
    credentials_need_refresh,
    read_aws_credentials,
    get_aws_credentials_path,
    get_temp_credentials_for_user,
    update_profile_credentials
)
import time

class AWSMonitor:
    def __init__(self, profile_name, manager_profile='iam-sorry'):
        self.profile_name = profile_name
        self.manager_profile = manager_profile
        self.session = None
        self._refresh_session()

    def _refresh_session(self):
        """Refresh credentials if needed and create new session."""
        creds_file = get_aws_credentials_path()
        config = read_aws_credentials(creds_file, auto_decrypt=True)

        if self.profile_name in config:
            profile = config[self.profile_name]
            needs_refresh, reason = credentials_need_refresh(profile, threshold_minutes=60)

            if needs_refresh:
                print(f"⚠ Refreshing credentials: {reason}")

                # Get IAM username
                iam_username = profile.get('credentials_owner')
                if iam_username:
                    # Refresh credentials (36 hours)
                    new_creds = get_temp_credentials_for_user(
                        self.manager_profile,
                        iam_username,
                        duration_seconds=36*3600
                    )
                    update_profile_credentials(
                        self.profile_name,
                        new_creds,
                        iam_username
                    )
                    print(f"✓ Credentials refreshed")

        # Create new session with (possibly refreshed) credentials
        self.session = create_session_with_profile(self.profile_name)

    def run(self):
        """Main monitoring loop."""
        while True:
            try:
                self._refresh_session()

                # Do monitoring work
                cloudwatch = self.session.client('cloudwatch')
                # ... your monitoring logic ...

                # Sleep 1 hour
                time.sleep(3600)

            except KeyboardInterrupt:
                print("Shutting down...")
                break
            except Exception as e:
                print(f"Error: {e}")
                time.sleep(60)  # Retry after 1 minute

if __name__ == '__main__':
    monitor = AWSMonitor('dirk-monitoring')
    monitor.run()

Security Considerations for Python API

  • SSH Passphrase: When using encrypted profiles, SSH key passphrase required (unless cached in ssh-agent)
  • Auto-Decryption: auto_decrypt=True automatically decrypts credentials on read
  • In-Memory Only: Decrypted credentials never written back to disk
  • Session Scoped: Use Method 1 (direct session) to avoid environment pollution
  • Error Handling: Catch exceptions for missing profiles, invalid credentials, expired sessions

Command Reference

Global Flags

--profile PROFILE           Manager profile (defaults to AWS_PROFILE env var)
--duration HOURS            Credential duration: 1-36 hours (default: 36)

Main Commands

# Generate temporary credentials
iam-sorry --profile usermanager admin
iam-sorry --profile usermanager --duration 12 admin

# Encrypt manager profile (one-time)
iam-sorry --profile usermanager --encrypt

# Show encrypted credentials
iam-sorry --show-encrypted usermanager

# Show decrypted credentials
iam-sorry --show-decrypted usermanager

# Output environment export statements
iam-sorry --eval usermanager

# Refresh default profile (prompts for confirmation)
AWS_PROFILE=usermanager iam-sorry

Manager Commands (Prefix-Based Access Control)

# Create and manage users in your namespace (auto-created if doesn't exist)
iam-sorry iam-bedrock           # Creates iam-bedrock, generates credentials
iam-sorry iam-analytics         # Creates iam-analytics, generates credentials

# The IAM user is created automatically if it doesn't exist
# Your namespace is based on your manager username prefix:
# - Manager 'iam-dirk' can create 'iam-bedrock', 'iam-analytics', etc.
# - Manager 'alice' can create 'alice-bedrock', 'alice-analytics', etc.

# Delegate user outside namespace to another owner (one-time only!)
iam-sorry jimmy-bedrock --chown jimmy    # Creates jimmy-bedrock, delegates to jimmy

Print Policy

# Show the recommended IAM policy using current Unix username as prefix
iam-sorry --print-policy

# Show the recommended IAM policy for a specific prefix
iam-sorry --print-policy iam
iam-sorry --print-policy alice

# Policy is personalized with your account ID and the specified namespace prefix
# Output includes:
# 1. JSON policy document
# 2. Step-by-step instructions for AWS administrator
# 3. How to attach the policy to a new IAM user via AWS Console

Next Steps:

  1. Copy the JSON policy
  2. Send to your AWS administrator
  3. Administrator creates a new IAM user (e.g., dp-mgr)
  4. Administrator adds the policy as an inline policy via AWS Console
  5. Receive Access Key ID and Secret Access Key
  6. Configure credentials: aws configure --profile iam-sorry
  7. Start using iam-sorry to manage your namespace users

Manager Guide: User Delegation with --chown

Use Case

Delegate user management to someone else (e.g., a student or teammate) without giving them manager credentials. The manager creates the user, configures it, then transfers ownership.

Example Workflow:

  • Manager dirk-admin creates user jimmy-bedrock for student Jimmy
  • Manager configures user via AWS console, adds to groups, sets permissions
  • Manager runs: ./iam-sorry --profile iam-sorry jimmy-bedrock --chown jimmy
  • Student Jimmy can then manage their own credentials
  • Manager loses write access (read-only viewing only)

How It Works

Step 1: Create and configure user

# Manager creates user and profile
dirk-admin$ ./iam-sorry --profile iam-sorry jimmy-bedrock

# Manager configures via AWS console:
# - Add user to security groups
# - Add to project teams
# - Set resource tags
# - Configure MFA (optional)

Step 2: Delegate to owner

# Manager delegates ownership (ONE-TIME operation)
dirk-admin$ ./iam-sorry --profile iam-sorry jimmy-bedrock --chown jimmy

# Output:
# ⚠ Delegating user 'jimmy-bedrock' to 'jimmy' (one-time operation)
# ✓ Applied delegation tags:
#   - owner: jimmy
#   - delegated-by: dirk-admin
# ℹ User delegated to 'jimmy' - they can now manage their own credentials

Step 3: Owner manages their own credentials

# Jimmy (owner) can now refresh their own credentials
jimmy$ ./iam-sorry --profile iam-sorry jimmy-bedrock

# Auto-refresh is enabled for batch operations
jimmy$ eval $(./iam-sorry --eval jimmy-bedrock)

Step 4: Original manager loses write access

# Manager tries to refresh jimmy's credentials
dirk-admin$ ./iam-sorry --profile iam-sorry jimmy-bedrock

# Output:
# ⚠ User 'jimmy-bedrock' is delegated to 'jimmy'
# You can view this user but cannot manage credentials (read-only access)

# Manager can view user info in AWS console, but cannot:
# - Create new access keys
# - Delete access keys
# - Remove tags
# - Re-delegate the user

Security Features

  • One-time only: Cannot re-delegate a user that's already delegated
  • Permanent ownership: Owner tag prevents manager from regaining access
  • Tag protection: Manager cannot remove ownership tags
  • Read-only fallback: Manager can still view delegated users
  • Audit trail: delegated-by tag tracks original manager

Requirements for Delegation

  1. Delegated owner must exist as IAM user (verified by CLI before delegation)
  2. Delegated user must match owner's prefix (e.g., cj-moin for owner cj)
  3. User must not have owner tag (prevents re-delegation)
  4. Manager must have CreateUsersDelegation permission (in generated IAM policy)

Security Architecture

Credential Types

Profile Type Encryption Risk Usage
usermanager Permanent ✓ Encrypted HIGH Manager only
bedrock Permanent ✗ Plaintext LOW Direct service
admin Temporary ✗ Plaintext LOW Batch ops

Encryption Details

Key Validation:

  • SSH key must be ED25519 (256-bit)
  • SSH key must be password-protected
  • Script validates automatically before encryption

Encryption Process:

SSH Private Key
    ↓ (HKDF-SHA256)
AES-256 Encryption Key (32 bytes)
    ↓
Credential Value + Random Nonce
    ↓ (AES-256-GCM)
__encrypted__:<base64>
    ↓
~/.aws/credentials (at-rest encrypted)
    ↓ (eval $(iam-sorry --eval usermanager))
Memory only (never on disk)
    ↓ (eval injects into environment)
AWS SDK access

Security Guarantees

  • No unprotected keys: SSH key must be password-protected
  • No plaintext on disk: Manager credentials stored encrypted
  • No disk persistence: Credentials only in memory during batch ops
  • Passphrase protected: SSH passphrase required each time
  • Random nonces: Different ciphertext each encryption
  • Lazy decryption: Only decrypt when needed
  • Auto-cleanup: Session tokens auto-expire (max 36 hours)

Username Prefix-Based Access Control

iam-sorry enforces username prefix matching to prevent unauthorized user management. Managers can only create and manage users whose names match their namespace prefix. Users within your namespace are automatically created if they don't exist.

How It Works:

  1. Extract Prefix: Everything before the first hyphen in the manager's username

    • dirk-admin → prefix is dirk
    • alice-manager → prefix is alice
    • bob (no hyphen) → prefix is bob
  2. Auto-Create Matching Users: When you request credentials for a user:

    • Prefix is validated locally (fast)
    • If IAM user doesn't exist, it's created automatically
    • Temporary credentials are generated for the new user
    • ~/.aws/config is populated with the region
  3. Allowed Operations: Manager can only manage users that:

    • Start with {prefix}- (e.g., dirk-admin can manage dirk-bedrock, dirk-analytics)
    • OR are exactly the prefix (e.g., dirk-admin can manage dirk)
    • BUT not themselves (e.g., dirk cannot create dirk)

Examples:

# Manager: dirk-admin (prefix: dirk)
iam-sorry --profile iam-sorry dirk-bedrock    # ✓ Allowed
iam-sorry --profile iam-sorry dirk-analytics  # ✓ Allowed
iam-sorry --profile iam-sorry dirk            # ✓ Allowed
iam-sorry --profile iam-sorry alice           # ✗ Denied (wrong prefix)
iam-sorry --profile iam-sorry bob-service     # ✗ Denied (wrong prefix)

# Manager: alice (prefix: alice)
iam-sorry --profile iam-sorry alice-ml        # ✓ Allowed
iam-sorry --profile iam-sorry alice-data      # ✓ Allowed
iam-sorry --profile iam-sorry alice           # ✗ Denied (cannot create self)

IAM Policy Enforcement:

The generated IAM policy enforces namespace restrictions on credential management:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CreateUsers",
      "Effect": "Allow",
      "Action": ["iam:CreateUser"],
      "Resource": ["arn:aws:iam::123456789012:user/dirk", "arn:aws:iam::123456789012:user/dirk-*"]
    },
    {
      "Sid": "CreateUsersDelegation",
      "Effect": "Allow",
      "Action": ["iam:CreateUser"],
      "Resource": "arn:aws:iam::*:user/*"
    },
    {
      "Sid": "ManageUserCredentials",
      "Effect": "Allow",
      "Action": ["iam:CreateAccessKey", "iam:DeleteAccessKey", "..."],
      "Resource": ["arn:aws:iam::123456789012:user/dirk", "arn:aws:iam::123456789012:user/dirk-*"]
    },
    {
      "Sid": "ManageRestrictionTagsDelegation",
      "Effect": "Allow",
      "Action": ["iam:TagUser", "iam:ListUserTags"],
      "Resource": "arn:aws:iam::*:user/*"
    }
  ]
}

Key Policy Design:

  • CreateUsers: Allows creating users in manager's namespace (dirk, dirk-*)
  • CreateUsersDelegation: Allows creating ANY user (for --chown delegation)
  • ManageUserCredentials: Restricted to namespace users only
  • ManageRestrictionTagsDelegation: Allows tagging ANY user (needed for delegation)
  • CLI Validation: Enforces prefix matching and validates owner existence
  • Permanent Restrictions: Deny UntagUser on namespace users (tags cannot be removed)

CLI Validation:

The tool validates prefix matching before attempting AWS API calls (unless using --chown):

$ iam-sorry --profile iam-sorry alice
Error: Username 'alice' does not match required prefix.
Manager 'dirk-admin' (prefix: 'dirk') can only manage users named 'dirk' or 'dirk-*'

$ iam-sorry --profile iam-sorry cj-moin --chown cj
⚠ Delegating user 'cj-moin' to 'cj' (one-time operation) Successfully updated profile 'cj-moin'

Security Benefits:

  • ✅ Managers can create users in other namespaces only with explicit --chown flag
  • ✅ Credential management restricted to namespace (CLI + IAM policy enforcement)
  • ✅ Delegation tags prevent accidental re-delegation
  • ✅ Clear ownership: dirk-admin owns all dirk-* users
  • ✅ Simplifies auditing: track which manager created and delegated which users

Tagging Strategy for Permanent Restrictions

The IAM policy includes a one-time tagging approach to enforce permanent restrictions:

How It Works:

  1. Manager can ADD tags (one-time setup):

    aws iam tag-user --user-name dirk-bedrock \
      --tags Key=manager-locked,Value=true Key=managed-by,Value=dirk-admin
    
  2. Manager can VIEW tags (for audit trail):

    aws iam list-user-tags --user-name dirk-bedrock
    
  3. Manager CANNOT remove tags (permanent lock):

    # This will be DENIED by the IAM policy
    aws iam untag-user --user-name dirk-bedrock --tag-keys manager-locked
    # Error: UnauthorizedOperation - UntagUser is denied for users in your namespace
    

IAM Policy Statements:

  • ManageRestrictionTags: Allows iam:TagUser and iam:ListUserTags on namespace users
  • PreventTagRemovalOrModification: Denies iam:UntagUser on namespace users

Use Case:

  • Manager sets up restriction tags when creating user
  • Manager can view tags to audit what restrictions are in place
  • Tags remain permanent (manager cannot remove them)
  • Provides tamper-proof audit trail
  • Prevents accidental or malicious tag removal

Configuration

SSH Key Requirements

Current System:

Key Type:     ED25519 (256-bit) ✓ EXCELLENT
Protection:   AES-256-CTR ✓ EXCELLENT
Permissions:  600 ✓ CORRECT

If Your Key is Unprotected:

ssh-keygen -p -f ~/.ssh/id_ed25519
# Enter old passphrase: [press Enter if none]
# Enter new passphrase: [type strong passphrase]
# Confirm new passphrase: [re-type]

Using ssh-agent for Passphrase Caching

# Start ssh-agent
eval $(ssh-agent -s)

# Add your key (you'll be prompted once for passphrase)
ssh-add ~/.ssh/id_ed25519

# Now encryption/decryption won't prompt for passphrase
iam-sorry --profile usermanager --encrypt
eval $(iam-sorry --eval usermanager)

# Kill agent when done
ssh-agent -k

Custom SSH Key Path

To use a different SSH key, modify the script:

# In iam-sorry tool, change:
def get_ssh_key_path():
    return os.path.expanduser("~/.ssh/id_ed25519")

# To:
def get_ssh_key_path():
    return os.path.expanduser("~/.ssh/my-custom-key")

Troubleshooting

"SSH key is not password protected"

Error: SSH key '/home/user/.ssh/id_ed25519' is not password protected.
For security, only password-protected SSH keys can be used for encryption.
Add a passphrase: ssh-keygen -p -f /home/user/.ssh/id_ed25519

Solution:

ssh-keygen -p -f ~/.ssh/id_ed25519
# Add a strong passphrase

"Cannot encrypt 'default' profile"

Error: Cannot encrypt 'default' profile. Use --profile or AWS_PROFILE
to specify an explicit profile name.

Solution: Always use an explicit profile name:

iam-sorry --profile usermanager --encrypt

"Could not determine IAM user"

Error: Could not determine IAM user for profile 'admin'

Solution: Create the profile first:

iam-sorry --profile usermanager admin

"Profile does not exist"

Error: Manager profile 'usermanager' not found in credentials file

Solution: Add the profile to ~/.aws/credentials:

[usermanager]
aws_access_key_id = AKIA...
aws_secret_access_key = ...

SSH Agent Not Available

⚠ ssh-agent not available or not running

Solution: Start ssh-agent:

eval $(ssh-agent -s)
ssh-add ~/.ssh/id_ed25519

Examples

Example 1: Setup & Encrypt

# 1. Install
pip install boto3 botocore cryptography

# 2. Add manager profile to ~/.aws/credentials
cat >> ~/.aws/credentials << 'EOF'
[usermanager]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
EOF

# 3. Encrypt
iam-sorry --profile usermanager --encrypt

# 4. Verify
iam-sorry --show-encrypted usermanager

Example 2: Batch IAM Provisioning

#!/bin/bash
# provision-users.sh

eval $(iam-sorry --eval usermanager)

for username in alice bob charlie; do
  echo "Creating user: $username"
  aws iam create-user --user-name $username

  echo "Creating access key for: $username"
  KEY=$(aws iam create-access-key --user-name $username \
    --query 'AccessKey.[AccessKeyId,SecretAccessKey]' --output text)

  echo "AccessKeyId: $(echo $KEY | cut -f1)"
  echo "SecretAccessKey: $(echo $KEY | cut -f2)"
done

unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

Example 3: Batch CloudFormation

#!/bin/bash
# deploy-stacks.sh

eval $(iam-sorry --eval usermanager)

REGIONS=("us-east-1" "us-west-2" "eu-west-1")
STACK_NAME="my-infrastructure"

for region in "${REGIONS[@]}"; do
  echo "Deploying to: $region"
  aws cloudformation create-stack \
    --region $region \
    --stack-name $STACK_NAME \
    --template-body file://template.yaml \
    --capabilities CAPABILITY_IAM
done

unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

Performance

  • Credential Generation: ~2-5 seconds (depends on IAM lookup)
  • Encryption: <100ms per credential
  • Decryption: <100ms per credential
  • Batch Operations: No overhead beyond normal AWS CLI/SDK

Compatibility

  • Python: 3.6+
  • OS: Linux, macOS, WSL2
  • AWS SDK: Works with any tool using AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN

Security Considerations

Best Practices

  1. Keep SSH Key Secure

    • Never share your SSH key
    • Use strong passphrase (20+ characters)
    • Store on secure hardware if possible
  2. Use SSH-Agent

    • Cache SSH passphrase with ssh-agent
    • Avoid typing passphrase repeatedly
    • Clear agent after batch operations
  3. Temporary Credentials Only

    • Rotate temporary credentials regularly
    • Use maximum practical duration
    • Let credentials auto-expire
  4. Batch Operations

    • Run in isolated bash sessions
    • Always unset environment variables after
    • Avoid storing credentials in scripts
  5. Monitoring

    • Enable CloudTrail for audit
    • Monitor credential usage
    • Alert on unusual activity

What This Tool DOES Protect

  • ✅ Manager credentials encrypted at-rest
  • ✅ Encryption key never stored (derived from SSH key)
  • ✅ Passphrase-protected SSH key required
  • ✅ Random nonces per encryption
  • ✅ Auto-expiring temporary credentials

What This Tool DOESN'T Protect

  • ❌ Shell history (credentials visible in ~/.bash_history)
  • ❌ Process listing (credentials visible in ps during execution)
  • ❌ SSH key compromise (if attacker gets SSH key, they get credentials)
  • ❌ Unencrypted profiles (bedrock, admin remain plaintext)

Contributing

Report issues or suggest improvements by opening a GitHub issue.

License

Specify your license here (MIT, Apache 2.0, etc.)

Author

Created with focus on practical AWS credential security for DevOps and infrastructure automation workflows.

Related Tools

  • aws-vault: Alternative credential encryption tool (uses OS keyring)
  • aws-cli: Official AWS command-line interface
  • aws-iam: AWS IAM management tool
  • aws-sso: AWS Identity Center for organizations
  • kinit / kerberos: For future Kerberos-based authentication integration
  • sssd: System Security Services Daemon for AD/Kerberos integration

FAQ

Q: Why ED25519 instead of RSA?

A: ED25519 is modern, provides 256-bit security, and has better performance than RSA 4096-bit.

Q: Can I use multiple SSH keys?

A: Currently the tool uses ~/.ssh/id_ed25519. You can modify the script to support custom paths.

Q: What if I forget my SSH passphrase?

A: You'll need to reset it with ssh-keygen -p. You may need to re-encrypt credentials with the new passphrase.

Q: Can I use this with AWS SSO?

A: No, this tool requires IAM user credentials. For organizations, AWS SSO is recommended instead.

Q: What happens if AWS STS is down?

A: Credential generation will fail. You can use cached temporary credentials or permanent credentials.

Q: Can I automate without passphrase prompts?

A: Yes, use ssh-agent to cache the passphrase. See "Using ssh-agent" section.

Q: Is this production-ready?

A: Yes, but test thoroughly in your environment. The encryption is solid, but credential management should always be tested carefully.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

iam_sorry-0.2.2.tar.gz (79.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

iam_sorry-0.2.2-py3-none-any.whl (53.6 kB view details)

Uploaded Python 3

File details

Details for the file iam_sorry-0.2.2.tar.gz.

File metadata

  • Download URL: iam_sorry-0.2.2.tar.gz
  • Upload date:
  • Size: 79.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for iam_sorry-0.2.2.tar.gz
Algorithm Hash digest
SHA256 0a1f7031535ac7aa20f859e14d02af80400e493e60660dbd3e8e14f535ff661f
MD5 f2e4df5dc723bc025962366efa345ebb
BLAKE2b-256 8526b33582b7d51d2ce88812d37e0e9c7f51d6c0705cee2b5a032c7a0bce65b7

See more details on using hashes here.

Provenance

The following attestation bundles were made for iam_sorry-0.2.2.tar.gz:

Publisher: publish-pypi.yml on dirkpetersen/iam-sorry

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iam_sorry-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: iam_sorry-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 53.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for iam_sorry-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4bde8877a942977b3b5153b3ad8583935a7b758bd31c201c4e6985422e258427
MD5 0c904501c86ef786d57a5b3e9d843875
BLAKE2b-256 d43763dc5fbcf8756131b8026bd171c5505d1446dd8136eaea2e291588238cc4

See more details on using hashes here.

Provenance

The following attestation bundles were made for iam_sorry-0.2.2-py3-none-any.whl:

Publisher: publish-pypi.yml on dirkpetersen/iam-sorry

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page