AWS CLI First #17: Zero-Secret CI/CD: Automating AWS Deployments with GitHub Actions & OIDC
Eliminate long-lived access keys in CI/CD: configure AWS IAM OpenID Connect (OIDC) identity provider, craft least-privilege trust policies for GitHub Actions, build an automated deployment pipeline, validate templates with cfn-lint, and invalidate CloudFront CDN caches from the terminal.
AWS with CLI: The Terminal-First Mastery Path
Part of the CLI-first comprehensive learning path from foundational setup to production cloud architectures.
Welcome to the grand finale of AWS with CLI: The Terminal-First Mastery Path.
Across the first 16 parts of this series, we designed, provisioned, hardened, and orchestrated production-grade AWS infrastructure without ever opening the AWS Management Console. We conquered VPC networking, multi-tier compute fleets, auto-scaling, distributed storage, managed databases, containers, serverless microservices, and declarative CloudFormation stacks.
However, in modern cloud engineering, running deployment commands manually from an engineer’s personal terminal workstation—even via scripted CLI—creates a production bottleneck. Production deployments must happen reliably, repeatably, and automatically through a Continuous Integration and Continuous Deployment (CI/CD) pipeline.
Yet, traditional CI/CD pipelines introduce one of the most critical security liabilities in cloud security: storing long-lived IAM Access Keys (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) as static GitHub repository secrets.
If a repository secret is compromised, leaked through a malicious pull request, or printed to build logs, attackers gain persistent, unrestricted access to your AWS infrastructure.
The modern cloud-native solution is OpenID Connect (OIDC) Federated Identity. With OIDC, your GitHub Actions runner exchanges a cryptographically signed, short-lived JSON Web Token (JWT) directly with AWS Security Token Service (STS) via sts:AssumeRoleWithWebIdentity. Zero static credentials are stored anywhere.
In this capstone guide, we will:
- Register GitHub as an IAM OIDC Identity Provider purely from the AWS CLI.
- Craft a mathematically locked-down IAM Role Trust Policy scoped exclusively to your GitHub organization, repository, and branch.
- Attach least-privilege deployment policies for CloudFormation, S3, and CloudFront.
- Construct a production GitHub Actions workflow (
.github/workflows/deploy.yml) that authenticates via OIDC, runscfn-lint, executes CloudFormation deployments, and invalidates CloudFront CDN caches. - Provide an automated teardown script and review the entire 17-part architect journey.
💰 Estimated Lab Cost & Security Architecture
1. Cost Breakdown for this Lab
All services configured in this capstone lab run within the AWS and GitHub Free Tiers:
| Resource | Quantity | Unit Cost (Hourly) | Estimated Lab Cost (1 Hour) |
|---|---|---|---|
| AWS IAM OIDC Identity Provider | 1 Provider | Free AWS Service | $0.000 |
| AWS STS Token Exchange | API Calls | Free AWS Feature | $0.000 |
| IAM Roles & Trust Policies | 1 Role | Free AWS Feature | $0.000 |
| GitHub Actions Runner (Linux) | ~5 Build Mins | GitHub Free Tier (2,000 min/mo) | $0.000 |
| CloudFront Invalidation | 1 Invalidation | First 1,000 paths/mo free | $0.000 |
| Total Estimated Lab Cost | ~$0.000 USD (100% Free) |
2. Zero-Secret OIDC Architecture Diagram
+----------------------------------------------------------------+
| GitHub Actions OIDC Authentication & Deploy |
+-------------------------------+--------------------------------+
|
v
+----------------------------------------------------------------+
| GitHub Actions Runner (Workflow Job) |
| 1. Requests signed OIDC JSON Web Token (JWT) from GitHub OIDC |
| 2. Token contains claims: sub (repo:org/app:ref:refs/heads/..)|
+-------------------------------+--------------------------------+
|
| [1] Send JWT (sts:AssumeRoleWithWebIdentity)
v
+----------------------------------------------------------------+
| AWS Security Token Service (STS) |
| - Validates GitHub OIDC CA Thumbprint |
| - Evaluates IAM Role Trust Policy (Audience & Subject Match) |
| - Issues Ephemeral Credentials (AWS_SESSION_TOKEN, 1 hour max)|
+-------------------------------+--------------------------------+
|
| [2] Return Temporary STS Credentials
v
+----------------------------------------------------------------+
| GitHub Actions Runner with Temporary AWS Credentials |
| 3. aws cloudformation deploy (Declarative Stack Rollout) |
| 4. aws s3 sync dist/ s3://bucket (Static Asset Upload) |
| 5. aws cloudfront create-invalidation (Global Edge Refresh) |
+----------------------------------------------------------------+
🛡️ Why Static Access Keys in CI/CD Are an Anti-Pattern
Before writing code, let’s examine why every major security benchmark (including CIS AWS Foundations and AWS Well-Architected Framework) strongly condemns storing static access keys in CI/CD:
| Vector | Static Access Keys (AKIA...) | OIDC Federated Identity |
|---|---|---|
| Credential Lifetime | Permanent (until manually rotated) | Ephemeral (typically 15–60 minutes) |
| Storage Location | Plaintext secret in GitHub repository settings | Never stored anywhere; minted in memory |
| Rotation Mechanism | Manual, error-prone, frequently ignored | Automatic per workflow run by AWS STS |
| Fork/PR Exploitation | If compromised, attacker has full off-site access | Scoped to specific repo, branch, or environment |
| Audit Trail | CloudTrail shows fixed IAM user activity | CloudTrail logs exact GitHub run ID, commit, and repo |
🔧 Step 1: Discovering & Registering the GitHub OIDC Provider via CLI
GitHub exposes its OpenID Connect discovery document at https://token.actions.githubusercontent.com/.well-known/openid-configuration.
AWS IAM requires:
- The Provider URL:
https://token.actions.githubusercontent.com - The Client ID / Audience:
sts.amazonaws.com - The Server Certificate Thumbprint: The SHA-1 hash of the OIDC identity provider’s root/intermediate CA certificate.
1. Extract the CA Thumbprint via OpenSSL and CLI
AWS accepts GitHub’s official intermediate root certificate thumbprints. You can calculate it directly in your terminal using openssl:
# Retrieve GitHub's OIDC certificate thumbprint
THUMBPRINT=$(echo | openssl s_client -servername token.actions.githubusercontent.com \
-showcerts -connect token.actions.githubusercontent.com:443 2>/dev/null \
| awk '/-----BEGIN CERTIFICATE-----/{cert=""} {cert=cert $0 "\n"} /-----END CERTIFICATE-----/{last=cert} END{printf "%s", last}' \
| openssl x509 -fingerprint -noout -sha1 \
| tr -d ':' | tr '[:upper:]' '[:lower:]' | cut -d'=' -f2)
echo "GitHub Actions OIDC Thumbprint: ${THUMBPRINT}"
[!TIP] GitHub Actions OIDC utilizes two canonical thumbprints verified by AWS:
6938fd4d98bab03faadb97b34396831e3780aea1and1c58a3a8518e8759bf075b76b750d4f2df264fcd. AWS IAM now automatically validates GitHub’s certificates, but supplying the thumbprint remains mandatory in the CLI API.
2. Create the OpenID Connect Provider
Let’s check if the provider already exists in your AWS account to maintain idempotency:
EXISTING_PROVIDER_ARN=$(aws iam list-open-id-connect-providers \
--query "OpenIDConnectProviderList[?contains(Arn, 'token.actions.githubusercontent.com')].Arn" \
--output text)
if [ -z "$EXISTING_PROVIDER_ARN" ]; then
echo "Creating GitHub Actions OIDC Provider..."
PROVIDER_ARN=$(aws iam create-open-id-connect-provider \
--url "https://token.actions.githubusercontent.com" \
--client-id-list "sts.amazonaws.com" \
--thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1" "1c58a3a8518e8759bf075b76b750d4f2df264fcd" \
--query "OpenIDConnectProviderArn" \
--output text)
echo "OIDC Provider Created: ${PROVIDER_ARN}"
else
echo "GitHub OIDC Provider already exists: ${EXISTING_PROVIDER_ARN}"
PROVIDER_ARN="${EXISTING_PROVIDER_ARN}"
fi
3. Verify the OIDC Provider Configuration
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn "${PROVIDER_ARN}" \
--output json
Terminal Output:
{
"Url": "token.actions.githubusercontent.com",
"ClientIDList": [
"sts.amazonaws.com"
],
"ThumbprintList": [
"6938fd4d98bab03faadb97b34396831e3780aea1",
"1c58a3a8518e8759bf075b76b750d4f2df264fcd"
],
"CreateDate": "2026-09-13T01:15:22.000Z"
}
🔐 Step 2: Crafting the Cryptographically Scoped IAM Trust Policy
The security of OIDC rests entirely on the IAM Role Trust Policy. If you write a permissive trust policy without filtering claims, any developer with a GitHub account could assume your AWS IAM role.
1. Understanding GitHub OIDC JWT Claims
When a GitHub Actions runner executes, its OIDC token includes standard claims:
aud: Audience, alwayssts.amazonaws.com.iss: Issuer,https://token.actions.githubusercontent.com.sub: Subject identifier in the formatrepo:<org>/<repo>:ref:refs/heads/<branch>orrepo:<org>/<repo>:environment:<env>.
2. Define Trust Policy Variables
Replace YOUR_GITHUB_ORG and YOUR_GITHUB_REPO with your actual GitHub username/organization and repository name:
GITHUB_ORG="rizkyprawira"
GITHUB_REPO="production-cloud-app"
DEPLOY_BRANCH="main"
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
ROLE_NAME="GitHubActionsDeployerRole"
3. Generate the Secure Trust Policy JSON
We enforce two conditions:
StringEquals: The audience must strictly matchsts.amazonaws.com.StringLike: The subject must originate only from themainbranch ofrizkyprawira/production-cloud-app.
cat <<EOF > trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:${GITHUB_ORG}/${GITHUB_REPO}:ref:refs/heads/${DEPLOY_BRANCH}"
}
}
}
]
}
EOF
[!WARNING] Never use
"token.actions.githubusercontent.com:sub": "*"in production! Wildcarding the subject claim permits any repository hosted on GitHub to assume this IAM role and seize control of your AWS account.
4. Create the Deployment IAM Role
aws iam create-role \
--role-name "${ROLE_NAME}" \
--assume-role-policy-document file://trust-policy.json \
--description "Federated role for GitHub Actions CI/CD deployments via OIDC" \
--query "Role.[RoleName, Arn]" \
--output table
Terminal Output:
---------------------------------------------------------------------------------------------------------
| CreateRole |
+--------------------------+----------------------------------------------------------------------------+
| GitHubActionsDeployerRole| arn:aws:iam::123456789012:role/GitHubActionsDeployerRole |
+--------------------------+----------------------------------------------------------------------------+
📜 Step 3: Attaching Least-Privilege Deployment Policies
Our CI/CD pipeline needs permission to:
- Validate and deploy CloudFormation stacks (
cloudformation:*). - Sync compiled frontend bundles to S3 (
s3:PutObject,s3:ListBucket). - Invalidate CloudFront edge distribution caches (
cloudfront:CreateInvalidation).
Let’s craft a scoped inline policy:
cat <<EOF > cicd-permissions-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudFormationDeployment",
"Effect": "Allow",
"Action": [
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackEvents",
"cloudformation:CreateChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:GetTemplateSummary"
],
"Resource": "arn:aws:cloudformation:*:${ACCOUNT_ID}:stack/production-*/*"
},
{
"Sid": "S3AssetSync",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::production-web-assets-*",
"arn:aws:s3:::production-web-assets-*/*"
]
},
{
"Sid": "CloudFrontCacheInvalidation",
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation"
],
"Resource": "*"
}
]
}
EOF
# Attach the policy to our deployment role
aws iam put-role-policy \
--role-name "${ROLE_NAME}" \
--policy-name "GitHubActionsDeploymentPolicy" \
--policy-document file://cicd-permissions-policy.json
Verify that the policy is attached:
aws iam list-role-policies \
--role-name "${ROLE_NAME}" \
--output text
🚀 Step 4: Crafting the Production GitHub Actions Workflow
Now let’s build the complete, production-ready GitHub Actions workflow file: .github/workflows/deploy.yml.
1. Key Workflow Directives
permissions: id-token: write: This is mandatory. Without this permission, the GitHub runner cannot request an OIDC token from GitHub’s token endpoint.aws-actions/configure-aws-credentials@v4: Official AWS action that handles the OIDC handshake, sends the JWT to STS, and exports standard AWS environment variables (AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN) into the runner environment.
2. Workflow Definition (.github/workflows/deploy.yml)
name: Production Deployment Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
# Grant minimal permissions required for OIDC token exchange and git checkout
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-southeast-1
ROLE_ARN: arn:aws:iam::123456789012:role/GitHubActionsDeployerRole
STACK_NAME: production-vpc-infra
jobs:
# ============================================================
# Stage 1: Quality Gate & CloudFormation Linting
# ============================================================
validate-and-test:
name: Lint & Validate Infrastructure
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Set up Python for cfn-lint
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install CloudFormation Linter
run: |
pip install cfn-lint awscli
- name: Run cfn-lint on CloudFormation Templates
run: |
echo "=== Linting CloudFormation Templates ==="
cfn-lint infrastructure/**/*.yaml
- name: Validate CloudFormation Syntax via AWS CLI
run: |
echo "=== Validating Template Structure ==="
aws cloudformation validate-template \
--template-body file://infrastructure/vpc-infrastructure.yaml \
--region ${{ env.AWS_REGION }}
# ============================================================
# Stage 2: Zero-Secret AWS Deployment via OIDC
# ============================================================
deploy-production:
name: Deploy to Production via OIDC
needs: validate-and-test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Authenticate to AWS via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
audience: sts.amazonaws.com
role-session-name: GitHubActions-${{ github.run_id }}
- name: Verify STS Identity (Audit Verification)
run: |
echo "=== Verifying Ephemeral STS Identity ==="
aws sts get-caller-identity --output table
- name: Deploy CloudFormation Stack
run: |
echo "=== Deploying CloudFormation Stack ==="
aws cloudformation deploy \
--template-file infrastructure/vpc-infrastructure.yaml \
--stack-name ${{ env.STACK_NAME }} \
--parameter-overrides Environment=Production VPCCIDR=10.0.0.0/16 \
--no-fail-on-empty-changeset \
--region ${{ env.AWS_REGION }}
- name: Invalidate CloudFront CDN Cache
run: |
DISTRIBUTION_ID=$(aws cloudformation describe-stacks \
--stack-name ${{ env.STACK_NAME }} \
--query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" \
--output text 2>/dev/null || echo "")
if [ -n "$DISTRIBUTION_ID" ] && [ "$DISTRIBUTION_ID" != "None" ]; then
echo "=== Triggering CloudFront Cache Invalidation for ${DISTRIBUTION_ID} ==="
INVALIDATION_ID=$(aws cloudfront create-invalidation \
--distribution-id "${DISTRIBUTION_ID}" \
--paths "/*" \
--query "Invalidation.Id" \
--output text)
echo "Cache Invalidation Triggered: ${INVALIDATION_ID}"
else
echo "No CloudFront distribution output found; skipping invalidation."
fi
🧪 Step 5: What Happens Under the Hood: The STS Handshake
To appreciate what aws-actions/configure-aws-credentials does, consider how AWS STS executes this exchange natively via the CLI.
When the GitHub runner starts:
- GitHub generates a signed JSON Web Token (JWT) using its private key:
{ "iss": "https://token.actions.githubusercontent.com", "aud": "sts.amazonaws.com", "sub": "repo:rizkyprawira/production-cloud-app:ref:refs/heads/main", "exp": 1726190400 } - The runner invokes the AWS STS endpoint:
aws sts assume-role-with-web-identity \ --role-arn "arn:aws:iam::123456789012:role/GitHubActionsDeployerRole" \ --role-session-name "GitHubCI-Session" \ --web-identity-token "$OIDC_JWT_TOKEN" \ --duration-seconds 3600 - STS verifies GitHub’s public RSA signature against the registered OIDC provider thumbprints.
- STS checks if
submatchesrepo:rizkyprawira/production-cloud-app:ref:refs/heads/main. - STS returns temporary AWS credentials:
{ "Credentials": { "AccessKeyId": "ASIAIOSFODNN7EXAMPLE", "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "SessionToken": "IQoJb3JpZ2luX2VjEOD...", "Expiration": "2026-09-13T02:15:22+00:00" }, "SubjectFromWebIdentityToken": "repo:rizkyprawira/production-cloud-app:ref:refs/heads/main" } - After 60 minutes, these credentials automatically expire into oblivion.
⚡ Step 6: Verifying Deployment Invalidation from the CLI
Once your pipeline deploys a new frontend build, you can inspect CloudFront invalidation status directly from your local terminal:
# List recent invalidations on your CloudFront distribution
aws cloudfront list-invalidations \
--distribution-id "E2EXAMPLE12345" \
--query "InvalidationList.Items[*].[Id, Status, CreateTime]" \
--output table
Terminal Output:
--------------------------------------------------------------------------
| ListInvalidations |
+-----------------------+------------+-----------------------------------+
| I1BEXAMPLE4567 | Completed | 2026-09-13T01:32:10.512000+00:00 |
| I2CEXAMPLE8901 | InProgress| 2026-09-13T01:45:00.128000+00:00 |
+-----------------------+------------+-----------------------------------+
You can wait for completion using the CLI waiter:
aws cloudfront wait invalidation-completed \
--distribution-id "E2EXAMPLE12345" \
--id "I2CEXAMPLE8901"
echo "CloudFront global edge caches successfully invalidated!"
🧹 Step 7: Clean Teardown Script
To clean up all IAM roles, inline policies, and the OIDC identity provider created during this lab, save and run this script:
cat <<'EOF' > teardown-oidc.sh
#!/usr/bin/env bash
set -euo pipefail
ROLE_NAME="GitHubActionsDeployerRole"
echo "=== 1. Deleting Inline Role Policies ==="
POLICIES=$(aws iam list-role-policies --role-name "${ROLE_NAME}" --query "PolicyNames[]" --output text 2>/dev/null || echo "")
for policy in $POLICIES; do
echo "Deleting policy: ${policy}..."
aws iam delete-role-policy --role-name "${ROLE_NAME}" --policy-name "${policy}"
done
echo "=== 2. Deleting IAM Deployment Role ==="
if aws iam get-role --role-name "${ROLE_NAME}" >/dev/null 2>&1; then
aws iam delete-role --role-name "${ROLE_NAME}"
echo "Deleted role: ${ROLE_NAME}"
fi
echo "=== 3. Deleting GitHub OIDC Identity Provider ==="
PROVIDER_ARN=$(aws iam list-open-id-connect-providers \
--query "OpenIDConnectProviderList[?contains(Arn, 'token.actions.githubusercontent.com')].Arn" \
--output text 2>/dev/null || echo "")
if [ -n "$PROVIDER_ARN" ]; then
aws iam delete-open-id-connect-provider --open-id-connect-provider-arn "${PROVIDER_ARN}"
echo "Deleted OIDC Provider: ${PROVIDER_ARN}"
fi
# Clean up local JSON files
rm -f trust-policy.json cicd-permissions-policy.json
echo "=== Teardown Complete! Zero lingering IAM objects or charges. ==="
EOF
chmod +x teardown-oidc.sh
./teardown-oidc.sh
🎓 Capstone Retrospective: The 17-Part Mastery Path
Congratulations! You have officially completed AWS with CLI: The Terminal-First Mastery Path.
Take a moment to reflect on the immense architectural journey you have completed across all 17 parts:
+--------------------------------------------------------------+
| THE 17-PART AWS CLI MASTERY CURRICULUM |
+--------------------------------------------------------------+
| |
| MODULE 1: FOUNDATIONS & IAM SECURITY |
| #01: CLI Installation, Root Bootstrap & Admin IAM Setup |
| #02: Multi-Account Profiles & Ephemeral STS AssumeRole |
| #03: Granular IAM Policies, Boundaries & Policy Simulator |
| |
| MODULE 2: GLOBAL INFRASTRUCTURE & VPC NETWORKING |
| #04: Global Regions, Availability Zones & Physical AZ IDs |
| #05: Production Multi-AZ VPC, Subnets & Route Tables |
| #06: Advanced VPC: S3 Endpoints, Flow Logs & Reachability |
| |
| MODULE 3: COMPUTE FLEETS & HIGH AVAILABILITY |
| #07: EC2 Instances, Stateful SG vs NACLs & IMDSv2 |
| #08: EBS Block Storage, KMS Encryption & Golden AMIs |
| #09: Zero-Trust SSM Session Manager & Fleet Run Command |
| #10: Layer 7 Traffic Routing: ALB, Target Groups & SSL |
| #11: Elastic Fleets: Auto Scaling, Spot & Rolling Refresh |
| |
| MODULE 4: STORAGE, DATABASES & DATA LIFECYCLE |
| #12: S3 Mastery: Bucket Policies, KMS, Lifecycles & EFS |
| #13: RDS PostgreSQL Multi-AZ & DynamoDB NoSQL with CLI |
| |
| MODULE 5: SERVERLESS, CONTAINERS & APIS |
| #14: Container Workflows: Docker ECR Push & ECS Fargate |
| #15: Serverless Microservices: Lambda & HTTP API Gateways |
| |
| MODULE 6: INFRASTRUCTURE AS CODE & CI/CD |
| #16: Multi-Tier Stacks with CloudFormation & SAM CLI |
| #17: Zero-Secret CI/CD: GitHub Actions OIDC & CloudFront |
| |
+--------------------------------------------------------------+
The 5 Golden Rules of the Terminal-First Cloud Architect
- Eliminate ClickOps Everywhere: Never create a production resource in the web console that isn’t reproducible, scriptable, or version-controlled.
- Enforce Least Privilege by Default: Grant permissions only to the exact actions, resources, and conditions required. Reject
Action: "*"andResource: "*". - Embrace Ephemeral Credentials: Never hardcode long-lived access keys in configuration files, build scripts, or CI secrets. Use IAM Roles, STS
AssumeRole, and OIDC. - Architect for Failure Across Multi-AZ: Assume individual instances, disks, and availability zones will fail. Rely on stateless compute, ASG self-healing, and load-balanced target groups.
- Declare Desired State: Move from imperative CLI commands for exploration to declarative CloudFormation / SAM templates for enterprise production.
You now possess the hands-on engineering muscle to architect, troubleshoot, deploy, and automate enterprise cloud infrastructure directly from the command line.
Keep your terminal open, your credentials ephemeral, and happy building on AWS!