AWS How-to Guides¶
Task recipes for AWS multi-account and network operations: turning on organization policies, landing zone setup, account vending, network sharing, workforce access, CLI recipes, monitoring, and troubleshooting. The reasons behind these steps are in Explanation. Quotas, prices, and policy-type tables are in Reference.
Placeholders and CLI version
Commands use AWS CLI v2 (2.37.x in September 2026). AWS CLI v1 has been in maintenance mode since 2026-07-15.
IDs such as r-xxxx, ou-xxxx-xxxxxxxx, p-xxxxxxxx, and 111122223333 are placeholders. Run organization-wide
commands from the management account or a delegated administrator account.
Deployment Patterns¶
Turn On Organization Policy Types¶
SCPs are on by default in an organization with all features. RCPs, declarative (EC2) policies, and the other management policy types must be turned on at the root first.
ROOT_ID=$(aws organizations list-roots --query 'Roots[0].Id' --output text)
# Resource control policies (RCPs)
aws organizations enable-policy-type --root-id "$ROOT_ID" --policy-type RESOURCE_CONTROL_POLICY
# Declarative policies for EC2, VPC, and EBS
aws organizations enable-policy-type --root-id "$ROOT_ID" --policy-type DECLARATIVE_POLICY_EC2
# Check which policy types are enabled
aws organizations list-roots --query 'Roots[0].PolicyTypes'
Create and Attach a Service Control Policy¶
This SCP stops member accounts from leaving the organization and from turning off CloudTrail. Test new SCPs on a test OU (a "policy staging" OU) before you attach them to production OUs.
cat > scp-baseline.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeaveOrg",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
},
{
"Sid": "ProtectCloudTrail",
"Effect": "Deny",
"Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "cloudtrail:UpdateTrail"],
"Resource": "*"
}
]
}
JSON
POLICY_ID=$(aws organizations create-policy \
--name baseline-guardrails \
--description "Deny leaving the org and disabling CloudTrail" \
--type SERVICE_CONTROL_POLICY \
--content file://scp-baseline.json \
--query 'Policy.PolicySummary.Id' --output text)
aws organizations attach-policy --policy-id "$POLICY_ID" --target-id ou-xxxx-xxxxxxxx
Quota headroom
Since May 2026 a node can have up to 10 SCPs, each up to 10,240 characters
(Reference). The default FullAWSAccess policy takes one slot.
Create a Resource Control Policy for a Data Perimeter¶
This RCP denies access to S3 objects from principals outside your organization. It exempts AWS service principals,
which act on your behalf (for example CloudTrail writing logs). Replace o-xxxxxxxxxx with your organization ID.
cat > rcp-s3-perimeter.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnforceOrgIdentities",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringNotEqualsIfExists": { "aws:PrincipalOrgID": "o-xxxxxxxxxx" },
"BoolIfExists": { "aws:PrincipalIsAWSService": "false" }
}
}
]
}
JSON
aws organizations create-policy \
--name rcp-s3-org-only \
--description "S3 reachable only by org principals and AWS services" \
--type RESOURCE_CONTROL_POLICY \
--content file://rcp-s3-perimeter.json
Check cross-org access first
Partners, vendor integrations, and public buckets that are shared on purpose break under this RCP. Review IAM Access Analyzer external-access findings, and add the needed exceptions before you attach it to production OUs.
Enforce EC2 Settings with a Declarative Policy¶
Declarative policies use @@assign operators under ec2_attributes. This one blocks new public AMI sharing and turns
off the EC2 serial console. Check the attribute names against the
EC2 policy syntax page
before you add more attributes.
cat > ec2-declarative.json <<'JSON'
{
"ec2_attributes": {
"image_block_public_access": {
"state": { "@@assign": "block_new_sharing" }
},
"serial_console_access": {
"status": { "@@assign": "disabled" }
},
"exception_message": {
"@@assign": "Blocked by org declarative policy. Contact the platform team."
}
}
}
JSON
aws organizations create-policy \
--name ec2-baseline \
--description "Block public AMI sharing and serial console" \
--type DECLARATIVE_POLICY_EC2 \
--content file://ec2-declarative.json
# Before you attach it, report the current state across an OU (the report is written to S3)
aws ec2 start-declarative-policies-report \
--s3-bucket my-org-reports-bucket \
--target-id ou-xxxx-xxxxxxxx
Centralize Root Access for Member Accounts¶
This removes the need for root passwords in member accounts. Privileged root-only tasks then run as short root sessions from the management account (or a delegated administrator).
# Trust IAM in the organization, then turn on both features
aws organizations enable-aws-service-access --service-principal iam.amazonaws.com
aws iam enable-organizations-root-credentials-management
aws iam enable-organizations-root-sessions
# Example root task: delete the root credentials of a member account
aws sts assume-root \
--target-principal 111122223333 \
--task-policy-arn arn=arn:aws:iam::aws:policy/root-task/IAMDeleteRootUserCredentials
Control Tower & Account Factory¶
AWS Control Tower automates landing zone setup with controls, central logging, and Account Factory for vending new accounts. Landing zone 4.0 (2025-11-17) makes the Config, CloudTrail, Backup, and SecurityRoles integrations optional (Reference).
Account Factory for Terraform (AFT) extends Control Tower with IaC-driven account provisioning.
# Show the landing zone and its version
aws controltower list-landing-zones
aws controltower get-landing-zone \
--landing-zone-identifier arn:aws:controltower:us-east-1:111122223333:landingzone/xxxxxxxxxxxx
# Upgrade an existing landing zone to 4.0 (the manifest lists governed Regions and integrations)
aws controltower update-landing-zone \
--landing-zone-identifier arn:aws:controltower:us-east-1:111122223333:landingzone/xxxxxxxxxxxx \
--landing-zone-version 4.0 \
--manifest file://landing-zone-manifest.json
# List the controls enabled on an OU
aws controltower list-enabled-controls \
--target-identifier arn:aws:organizations::111122223333:ou/o-xxxxxxxxxx/ou-xxxx-xxxxxxxx
# Enable a managed control on an OU (control ARN from the Control Catalog)
aws controltower enable-control \
--control-identifier arn:aws:controlcatalog:::control/xxxxxxxxxxxxxxxxxxxxxxxxx \
--target-identifier arn:aws:organizations::111122223333:ou/o-xxxxxxxxxx/ou-xxxx-xxxxxxxx
# List accounts in the organization
aws organizations list-accounts --output table
Upgrading to 4.0
Read the landing zone 4.0 migration guide first. Tools that drive Control Tower, such as Landing Zone Accelerator, must support 4.0 before you upgrade. LZA added 4.0 support in v1.14.0 (2025-11-24).
Service Catalog¶
Service Catalog provides self-service product provisioning with governance. Platform teams define approved CloudFormation templates as products. Application teams launch them without direct CloudFormation access.
# List available products in a portfolio
aws servicecatalog search-products \
--filters FullTextSearch=vpc
# Provision a product
aws servicecatalog provision-product \
--product-id prod-XXXXXXXXXXXX \
--provisioned-product-name my-vpc \
--provisioning-artifact-id pa-XXXXXXXXXXXX \
--provisioning-parameters Key=VpcCidr,Value=10.0.0.0/16
CloudFormation StackSets¶
StackSets deploy CloudFormation stacks across multiple accounts and regions from a single template.
# Create a StackSet with service-managed permissions (Organizations integration)
aws cloudformation create-stack-set \
--stack-set-name baseline-config-rules \
--template-body file://config-rules.yaml \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
# Deploy to all accounts in an OU
aws cloudformation create-stack-instances \
--stack-set-name baseline-config-rules \
--deployment-targets OrganizationalUnitIds=ou-xxxx-xxxxxxxx \
--regions us-east-1 us-west-2
Share a Transit Gateway with Workload Accounts¶
The Network account owns the TGW and shares it through AWS RAM. Workload accounts then create their own VPC attachments.
# Network account (once per organization): allow RAM sharing inside the org
aws ram enable-sharing-with-aws-organization
# Network account: share the TGW with a whole OU
aws ram create-resource-share \
--name tgw-us-east-1 \
--resource-arns arn:aws:ec2:us-east-1:111122223333:transit-gateway/tgw-0xxxxxxxxxxxxxxxx \
--principals arn:aws:organizations::111122223333:ou/o-xxxxxxxxxx/ou-xxxx-xxxxxxxx
# Workload account: attach a VPC using one /28 subnet per AZ
aws ec2 create-transit-gateway-vpc-attachment \
--transit-gateway-id tgw-0xxxxxxxxxxxxxxxx \
--vpc-id vpc-0xxxxxxxxxxxxxxxx \
--subnet-ids subnet-0aaaaaaaaaaaaaaaa subnet-0bbbbbbbbbbbbbbbb
# Network account: accept the attachment if auto-accept is disabled on the TGW
aws ec2 accept-transit-gateway-vpc-attachment \
--transit-gateway-attachment-id tgw-attach-0xxxxxxxxxxxxxxxx
Manage Workforce Access with IAM Identity Center¶
Assign permission sets to IdP groups per account, and give engineers short-lived CLI credentials.
# Find the Identity Center instance ARN and identity store ID
aws sso-admin list-instances
# List permission sets
aws sso-admin list-permission-sets \
--instance-arn arn:aws:sso:::instance/ssoins-XXXXXXXXXXXX
# Assign a permission set to a group for an account
aws sso-admin create-account-assignment \
--instance-arn arn:aws:sso:::instance/ssoins-XXXXXXXXXXXX \
--target-id 123456789012 \
--target-type AWS_ACCOUNT \
--permission-set-arn arn:aws:sso:::permissionSet/ssoins-XXXXXXXXXXXX/ps-XXXXXXXXXXXX \
--principal-type GROUP \
--principal-id XXXXXXXXXXXX
# Re-provision a changed permission set to every account that uses it
aws sso-admin provision-permission-set \
--instance-arn arn:aws:sso:::instance/ssoins-XXXXXXXXXXXX \
--permission-set-arn arn:aws:sso:::permissionSet/ssoins-XXXXXXXXXXXX/ps-XXXXXXXXXXXX \
--target-type ALL_PROVISIONED_ACCOUNTS
# Engineer workstation: configure and use an SSO profile (no long-lived keys)
aws configure sso --profile team-a-dev
aws sso login --profile team-a-dev
aws sts get-caller-identity --profile team-a-dev
CLI & SDK¶
VPC & Networking¶
# List all VPCs
aws ec2 describe-vpcs --output table --query 'Vpcs[*].[VpcId,CidrBlock,Tags[?Key==`Name`].Value|[0]]'
# List subnets for a VPC
aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-0xxxxxxxxxxxxxxxxx \
--query 'Subnets[*].[SubnetId,CidrBlock,AvailabilityZone]' --output table
# Describe a Transit Gateway's route table
aws ec2 search-transit-gateway-routes \
--transit-gateway-route-table-id tgw-rtb-0xxxxxxxxxxxxxxxxx \
--filters Name=state,Values=active
EC2¶
# List running instances with key details
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=running \
--query 'Reservations[*].Instances[*].[InstanceId,InstanceType,PrivateIpAddress,Tags[?Key==`Name`].Value|[0]]' \
--output table
# Create an AMI from a running instance
aws ec2 create-image \
--instance-id i-0xxxxxxxxxxxxxxxxx \
--name "pre-deploy-$(date +%Y-%m-%d)" \
--no-reboot
EKS¶
# List clusters
aws eks list-clusters
# Update kubeconfig for a cluster
aws eks update-kubeconfig --name my-cluster --region us-east-1
# Describe a node group
aws eks describe-nodegroup --cluster-name my-cluster --nodegroup-name workers
RDS¶
# List all RDS instances
aws rds describe-db-instances \
--query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,MultiAZ]' \
--output table
# Create a manual snapshot
aws rds create-db-snapshot \
--db-instance-identifier prod-db \
--db-snapshot-identifier prod-db-$(date +%Y%m%d)
# Initiate a failover for a Multi-AZ instance
aws rds reboot-db-instance \
--db-instance-identifier prod-db \
--force-failover
CloudFormation¶
# Deploy a stack
aws cloudformation deploy \
--template-file infra.yaml \
--stack-name prod-infra \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides Environment=prod VpcCidr=10.0.0.0/16
# List stack events (troubleshoot failed deployments)
aws cloudformation describe-stack-events \
--stack-name prod-infra \
--query 'StackEvents[?ResourceStatus==`CREATE_FAILED`].[LogicalResourceId,ResourceStatusReason]' \
--output table
Monitoring & Alerting¶
CloudWatch¶
CloudWatch collects metrics, logs, and traces. Custom metrics can be
published via put-metric-data.
# Query EC2 CPU utilization (last hour, 5-minute periods). GNU date; on macOS use: date -u -v-1H ...
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0xxxxxxxxxxxxxxxxx \
--start-time $(date -u -d '-1 hour' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Average
# Create a CloudWatch alarm
aws cloudwatch put-metric-alarm \
--alarm-name "high-cpu-prod" \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0xxxxxxxxxxxxxxxxx \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 3 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts
CloudTrail¶
CloudTrail records all API calls across all AWS services. For multi-account setups, create an organization trail from the management account.
# Look up recent API events
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=StopInstances \
--max-results 10
AWS Config¶
Config continuously evaluates resource compliance against rules. Use conformance packs for bundled rule sets.
# Check compliance status of a rule
aws configservice get-compliance-details-by-config-rule \
--config-rule-name s3-bucket-server-side-encryption-enabled \
--compliance-types NON_COMPLIANT
GuardDuty¶
GuardDuty uses ML to detect threats across CloudTrail, VPC Flow Logs, and DNS logs. Enable in every region used by the organization.
# List high-severity findings
aws guardduty list-findings \
--detector-id XXXXXXXXXXXXXXXXXXXX \
--finding-criteria '{"Criterion":{"severity":{"Gte":7}}}'
Security Hub aggregation
Enable Security Hub CSPM (the service called "Security Hub" before 2025-12) in the Audit account as delegated administrator. It aggregates findings from GuardDuty, Inspector, Config, Macie, and Firewall Manager, with compliance scores (CIS, PCI DSS, AWS Foundational Security Best Practices). The new AWS Security Hub (GA 2025-12-02) adds correlation and prioritization of findings on top.
Store and Rotate a Secret¶
Keep database credentials in Secrets Manager and rotate them on a schedule.
# Create a secret
aws secretsmanager create-secret \
--name prod/db/password \
--secret-string '{"username":"admin","password":"XXXXXXXX"}'
# Enable automatic rotation (every 30 days)
aws secretsmanager rotate-secret \
--secret-id prod/db/password \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:rotate-db-secret \
--rotation-rules AutomaticallyAfterDays=30
Troubleshooting¶
VPC Connectivity¶
| Symptom | Likely Cause | Resolution |
|---|---|---|
| EC2 cannot get to the Internet | Missing NAT Gateway route or no IGW | Check route tables: private subnets need 0.0.0.0/0 -> nat-gw. Public subnets need 0.0.0.0/0 -> igw |
| Cannot get to resources in peered VPC | Missing route entries in both VPCs | Add routes for the peer CIDR pointing to the peering connection in both VPC route tables |
| Cross-AZ latency higher than expected | Traffic hairpinning through a single-AZ NAT Gateway | Deploy NAT Gateways in each AZ and update route tables per AZ |
IAM Permission Debugging¶
# Simulate whether a principal can perform an action
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/deploy-role \
--action-names s3:PutObject \
--resource-arns arn:aws:s3:::my-bucket/*
# Decode an encoded authorization failure message
aws sts decode-authorization-message --encoded-message <encoded-message>
Reading AccessDenied messages
For most services, the AccessDenied message names the policy type that blocked the call, for example
"with an explicit deny in a service control policy" or "...in a resource control policy". Search CloudTrail
for the event: the errorCode and errorMessage fields show the same result. Remember that SCPs filter the
caller's account and RCPs filter the resource's account. A cross-account call can be blocked by either one.
Transit Gateway Routing¶
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Spoke VPCs cannot get to each other | TGW route table missing propagation or static routes | Verify route table associations: aws ec2 get-transit-gateway-route-table-associations. Enable route propagation for each attachment. |
| Traffic not flowing through inspection VPC | Appliance mode not enabled on TGW attachment | Enable appliance mode: aws ec2 modify-transit-gateway-vpc-attachment --options ApplianceModeSupport=enable |
| Return traffic takes a different path (asymmetric) | Subnet route tables not updated for return path | Make sure that VPC ingress routing and TGW return routes both point through the firewall ENI |
General Diagnostic Commands¶
# Check VPC Flow Logs (requires flow log enabled on VPC/subnet/ENI)
aws logs filter-log-events \
--log-group-name /vpc/flow-logs \
--filter-pattern "REJECT" \
--start-time $(date -d "-1 hour" +%s000)
# Show the firewall policy attached to a Network Firewall
aws network-firewall describe-firewall \
--firewall-name central-inspection \
--query 'Firewall.FirewallPolicyArn'
# Check Security Group rules applied to an ENI
aws ec2 describe-security-group-rules \
--filters Name=group-id,Values=sg-0xxxxxxxxxxxxxxxxx \
--output table