IAM & Identity Security

Credential Management Best Practices

4 min read

Credentials are the primary attack vector for cloud breaches. A 2024 Palo Alto Networks study found over 90,000 leaked .env files, with 1,185 containing active AWS access keys. Proper credential management is non-negotiable.

The Credential Hierarchy

Not all credentials are equal. Prioritize based on risk:

Credential Type Risk Level Recommendation
Root account Critical MFA hardware key, no access keys ever
Admin IAM users High MFA required, time-limited sessions
Service accounts High Roles over keys, rotate regularly
Developer keys Medium Short-lived, monitored, limited scope
Application tokens Medium Managed secrets, automatic rotation

Eliminating Long-Lived Credentials

AWS: Use Roles Instead of Keys

The problem:

# Developer .env file (FOUND IN 90,000+ leaked files)
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

The solution - EC2 Instance Roles:

# CloudFormation - Attach role to EC2
Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref MyInstanceProfile
      # No credentials in instance - they're provided by STS

  MyInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref MyRole

  MyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole

Azure: Use Managed Identities

The problem:

# Service principal with client secret
az ad sp create-for-rbac --name "MyApp" --password "SuperSecretPassword123"

The solution - Managed Identity:

# Create VM with system-assigned managed identity
az vm create \
    --name MyVM \
    --resource-group MyRG \
    --assign-identity '[system]'

# Application code - no credentials needed
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()  # Automatically uses managed identity

GCP: Use Workload Identity

The problem:

# Downloaded service account key (security nightmare)
gcloud iam service-accounts keys create key.json
export GOOGLE_APPLICATION_CREDENTIALS=./key.json

The solution - Workload Identity for GKE:

# Kubernetes service account with GCP binding
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-ksa
  annotations:
    iam.gke.io/gcp-service-account: my-sa@project.iam.gserviceaccount.com
# Bind KSA to GSA
gcloud iam service-accounts add-iam-policy-binding \
    my-sa@project.iam.gserviceaccount.com \
    --role="roles/iam.workloadIdentityUser" \
    --member="serviceAccount:project.svc.id.goog[namespace/my-ksa]"

Secrets Management

When credentials are necessary, manage them properly:

AWS Secrets Manager

import boto3
from botocore.exceptions import ClientError

def get_secret(secret_name):
    client = boto3.client('secretsmanager')
    response = client.get_secret_value(SecretId=secret_name)
    return response['SecretString']

# Enable automatic rotation
# aws secretsmanager rotate-secret --secret-id MySecret --rotation-rules ...

Azure Key Vault

from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://my-vault.vault.azure.net/", credential=credential)

secret = client.get_secret("DatabasePassword")
password = secret.value

HashiCorp Vault

# Store secret
vault kv put secret/myapp/config db_password="secret123"

# Retrieve with TTL-based access
vault kv get -format=json secret/myapp/config

Key Rotation Strategies

AWS Access Key Rotation

#!/bin/bash
# Rotate access keys for IAM user

USER="application-user"

# Create new key
NEW_KEY=$(aws iam create-access-key --user-name $USER --query 'AccessKey')

# Update application with new key (app-specific)
# ...

# Test new key works
# ...

# Delete old key
aws iam delete-access-key --user-name $USER --access-key-id $OLD_KEY_ID

Automated Rotation with Lambda

import boto3
import json

def lambda_handler(event, context):
    sm_client = boto3.client('secretsmanager')
    secret_id = event['SecretId']
    step = event['Step']

    if step == 'createSecret':
        # Generate new credentials
        new_password = generate_secure_password()
        sm_client.put_secret_value(
            SecretId=secret_id,
            SecretString=json.dumps({'password': new_password}),
            VersionStages=['AWSPENDING']
        )

    elif step == 'setSecret':
        # Update the service with new credentials
        update_database_password(secret_id)

    elif step == 'testSecret':
        # Verify new credentials work
        test_connection(secret_id)

    elif step == 'finishSecret':
        # Promote pending to current
        sm_client.update_secret_version_stage(
            SecretId=secret_id,
            VersionStage='AWSCURRENT',
            MoveToVersionId=event['ClientRequestToken']
        )

Emergency Credential Response

When credentials are compromised:

Action AWS Command Priority
Disable key immediately aws iam update-access-key --status Inactive 1
Revoke active sessions Delete role's inline policy, force re-auth 2
Audit CloudTrail Search for compromised key usage 3
Rotate all related secrets Update dependent systems 4
Investigate root cause How was key leaked? 5
# Emergency: Disable all access keys for a user
aws iam list-access-keys --user-name compromised-user | \
jq -r '.AccessKeyMetadata[].AccessKeyId' | \
xargs -I {} aws iam update-access-key --user-name compromised-user \
    --access-key-id {} --status Inactive

Next, we'll explore authentication hardening with MFA and conditional access policies. :::

Quiz

Module 2: IAM & Identity Security

Take Quiz