Containers and IaC
AWS
CLI usage, credential resolution, and the per-service commands used most during operations.
Cheatsheet #
| Task | Command |
|---|---|
| Who am I | aws sts get-caller-identity |
| Which profile and region | aws configure list |
| Assume a role | aws sts assume-role --role-arn ... --role-session-name x |
| Filter output | --query 'Reservations[].Instances[].InstanceId' --output text |
| Shell on an instance, no SSH | aws ssm start-session --target i-0abc123 |
| Tail a log group | aws logs tail /aws/lambda/fn --follow |
| Recent CloudTrail events | aws cloudtrail lookup-events --max-results 20 |
| Sync a bucket | aws s3 sync ./dir s3://bucket/prefix --delete |
| Presigned URL | aws s3 presign s3://bucket/key --expires-in 3600 |
| Decode an authorisation error | aws sts decode-authorization-message --encoded-message ... |
| Simulate a policy | aws iam simulate-principal-policy --policy-source-arn ... --action-names s3:GetObject |
| Service quotas | aws service-quotas list-service-quotas --service-code ec2 |
| Wait for a state | aws ec2 wait instance-running --instance-ids i-0abc123 |
Credentials and identity #
The CLI resolves credentials in order: command-line options, environment variables, the profile’s credential_process or SSO cache, ~/.aws/credentials, container credentials, then instance metadata. The first source that answers wins — which is why a stale AWS_ACCESS_KEY_ID in a shell quietly beats a correct profile.
aws sts get-caller-identity # the only reliable answer to "which account is this"
aws configure list # shows the source of each setting
env | grep ^AWS_
AWS_PROFILE=prod aws s3 ls# ~/.aws/config
[profile prod]
sso_session = corp
sso_account_id = 123456789012
sso_role_name = PlatformEngineer
region = ap-southeast-2
output = json
[profile prod-admin]
source_profile = prod
role_arn = arn:aws:iam::123456789012:role/Admin
mfa_serial = arn:aws:iam::123456789012:mfa/jodis
duration_seconds = 3600
[sso-session corp]
sso_start_url = https://example.awsapps.com/start
sso_region = ap-southeast-2aws sso login --profile prod
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/Deploy --role-session-name ci --query CredentialsLong-lived access keys are the usual root cause of incidents
Use SSO for people and instance or IRSA roles for workloads. If a static key must exist, rotate it on a schedule and keep it out of repositories, images and echoed CI variables.
Output and queries #
--query is JMESPath applied by the CLI after the API responds; --filters is applied by the service before responding. On a large account the difference is minutes.
aws ec2 describe-instances --filters 'Name=instance-state-name,Values=running' \
--query 'Reservations[].Instances[].[InstanceId,InstanceType,PrivateIpAddress]' --output text
aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,Tags[?Key==`Name`].Value|[0]]' --output table
aws s3api list-objects-v2 --bucket b --query 'sort_by(Contents, &LastModified)[-5:].[Key,Size]' --output text
aws ec2 describe-instances --page-size 100 --max-items 1000 # explicit pagination--output text is tab-separated and pipes into awk directly; --output json | jq is better when the shape is nested.
EC2 #
aws ec2 describe-instances --filters 'Name=tag:Environment,Values=prod' \
--query 'Reservations[].Instances[].[InstanceId,PrivateIpAddress,State.Name]' --output text
aws ec2 start-instances --instance-ids i-0abc123
aws ec2 stop-instances --instance-ids i-0abc123
aws ec2 get-console-output --instance-id i-0abc123 --output text | tail -50
aws ec2 describe-instance-status --instance-id i-0abc123 # system and instance checks
aws ec2 create-image --instance-id i-0abc123 --name "backup-$(date +%F)" --no-reboot
aws ssm start-session --target i-0abc123 # no key, no bastion, auditedSecurity groups are stateful, so an inbound allow implies the return path. Network ACLs are stateless and need both directions — that asymmetry explains most “the security group looks correct” cases.
S3 #
aws s3 ls s3://bucket/prefix/ --human-readable --summarize
aws s3 sync ./dist s3://bucket/site --delete --dryrun # always dry run --delete
aws s3 cp file s3://bucket/key --storage-class INTELLIGENT_TIERING
aws s3 presign s3://bucket/key --expires-in 3600
aws s3api head-object --bucket b --key k # size, encryption, metadata
aws s3api get-bucket-policy --bucket b --query Policy --output text | jq
aws s3api list-object-versions --bucket b --prefix k --query 'Versions[].[Key,VersionId,IsLatest]' --output text
aws s3api put-public-access-block --bucket b --public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=trueaws s3 is the high-level interface (sync, recursive copy, multipart); aws s3api is the raw API for policies, versions and metadata.
IAM #
Evaluation order: an explicit Deny anywhere wins, then an Allow must exist in an identity or resource policy, and SCPs, permission boundaries and session policies each cap what is possible. “Denied with a policy that clearly allows it” is nearly always an SCP or boundary.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/Deploy \
--action-names s3:PutObject --resource-arns 'arn:aws:s3:::bucket/key'
aws sts decode-authorization-message --encoded-message "$MSG" --query DecodedMessage --output text | jq
aws iam list-attached-role-policies --role-name Deploy
aws iam get-account-authorization-details > iam-dump.json # everything, for offline review{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::example-bucket/prod/*",
"Condition": { "StringEquals": { "aws:PrincipalTag/Team": "platform" } }
}]
}Logs and audit #
aws logs tail /aws/lambda/fn --follow --since 10m --format short
aws logs tail /aws/eks/prod/cluster --filter-pattern 'ERROR'
aws logs start-query --log-group-name /aws/lambda/fn \
--start-time "$(date -d '1 hour ago' +%s)" --end-time "$(date +%s)" \
--query-string 'fields @timestamp, @message | filter @message like /ERROR/ | sort @timestamp desc | limit 20'
aws logs get-query-results --query-id <id>
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
--query 'Events[].[EventTime,Username,Resources[0].ResourceName]' --output textCloudTrail records control-plane calls by default; data events such as S3 object access must be enabled per trail before they exist to search.
RDS, Lambda, EKS #
aws rds describe-db-instances --query 'DBInstances[].[DBInstanceIdentifier,DBInstanceStatus,Endpoint.Address]' --output table
aws rds create-db-snapshot --db-instance-identifier prod --db-snapshot-identifier "pre-change-$(date +%F)"
aws rds describe-events --source-identifier prod --source-type db-instance --duration 1440
aws lambda invoke --function-name fn --payload '{"k":"v"}' --cli-binary-format raw-in-base64-out out.json
aws lambda get-function-configuration --function-name fn --query '[Timeout,MemorySize,LastUpdateStatus]'
aws eks update-kubeconfig --name prod --region ap-southeast-2
aws eks describe-cluster --name prod --query 'cluster.[version,status,endpoint]'
aws eks list-nodegroups --cluster-name prodSnapshot before any change to a database you cannot rebuild: minutes of cost, and it removes the worst outcome.
Cost #
aws ce get-cost-and-usage --time-period Start=2024-08-01,End=2024-09-01 \
--granularity MONTHLY --metrics UnblendedCost --group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[].Groups[].[Keys[0],Metrics.UnblendedCost.Amount]' --output text | sort -k2 -rn | head
aws ec2 describe-volumes --filters Name=status,Values=available --query 'Volumes[].[VolumeId,Size]' --output text
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null].PublicIp' --output textOneliners #
# Confirm account and region before anything destructive
aws sts get-caller-identity --query '[Account,Arn]' --output text; aws configure get region
# Running instances in every region
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do aws ec2 describe-instances --region "$r" --filters Name=instance-state-name,Values=running --query 'Reservations[].Instances[].[InstanceId,InstanceType]' --output text | sed "s/^/$r /"; done
# Instance ID from a Name tag
aws ec2 describe-instances --filters 'Name=tag:Name,Values=web-01' --query 'Reservations[0].Instances[0].InstanceId' --output text
# Security groups open to the internet
aws ec2 describe-security-groups --query 'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupId,GroupName]' --output text
# Buckets with no default encryption
for b in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do aws s3api get-bucket-encryption --bucket "$b" >/dev/null 2>&1 || echo "$b"; done
# Largest objects in a bucket
aws s3api list-objects-v2 --bucket b --query 'sort_by(Contents,&Size)[-10:].[Size,Key]' --output text
# Access keys older than 90 days
aws iam list-users --query 'Users[].UserName' --output text | tr '\t' '\n' | while read -r u; do aws iam list-access-keys --user-name "$u" --query "AccessKeyMetadata[?CreateDate<='$(date -d '90 days ago' +%Y-%m-%d)'].[UserName,AccessKeyId,CreateDate]" --output text; done
# Who terminated an instance
aws cloudtrail lookup-events --lookup-attributes AttributeKey=ResourceName,AttributeValue=i-0abc123 --query 'Events[].[EventTime,EventName,Username]' --output text
# Tail a Lambda's logs while invoking it
aws logs tail /aws/lambda/fn --follow & aws lambda invoke --function-name fn --payload '{}' /dev/stdout
# Copy between buckets without downloading
aws s3 sync s3://src/prefix s3://dst/prefix --source-region us-east-1 --region ap-southeast-2
# Every tag value in use for a key
aws ec2 describe-tags --filters Name=key,Values=Environment --query 'Tags[].Value' --output text | tr '\t' '\n' | sort | uniq -c
# Check a quota before scaling
aws service-quotas get-service-quota --service-code ec2 --quota-code L-1216C47A --query 'Quota.Value'