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. :::