diff --git a/tuts/094-aws-cloudtrail-gs/README.md b/tuts/094-aws-cloudtrail-gs/README.md new file mode 100644 index 00000000..69cff459 --- /dev/null +++ b/tuts/094-aws-cloudtrail-gs/README.md @@ -0,0 +1,56 @@ +# CloudTrail: Enable logging and look up events + +Create a CloudTrail trail that logs API activity to an S3 bucket, look up recent events, and clean up. + +## Source + +https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-tutorial.html + +## Use case + +- ID: cloudtrail/getting-started +- Phase: create +- Complexity: beginner +- Core actions: cloudtrail:CreateTrail, cloudtrail:StartLogging, cloudtrail:LookupEvents + +## What it does + +1. Creates an S3 bucket for trail logs +2. Sets the bucket policy to allow CloudTrail writes +3. Creates a trail pointing to the bucket +4. Starts logging +5. Looks up recent API events +6. Describes the trail configuration + +## Running + +```bash +bash aws-cloudtrail-gs.sh +``` + +To auto-run with cleanup: + +```bash +echo 'y' | bash aws-cloudtrail-gs.sh +``` + +## Resources created + +- CloudTrail trail +- S3 bucket (with CloudTrail bucket policy) + +## Estimated time + +- Run: ~10 seconds + +## Cost + +S3 storage only. CloudTrail delivers management event logs to S3 at no charge for the first trail. S3 storage costs apply for the log files. + +## Related docs + +- [Getting started with CloudTrail tutorials](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-tutorial.html) +- [Creating a trail](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-create-and-update-a-trail.html) +- [Amazon S3 bucket policy for CloudTrail](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/create-s3-bucket-policy-for-cloudtrail.html) +- [Looking up events with LookupEvents](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/view-cloudtrail-events-cli.html) +- [CloudTrail pricing](https://aws.amazon.com/cloudtrail/pricing/) diff --git a/tuts/094-aws-cloudtrail-gs/REVISION-HISTORY.md b/tuts/094-aws-cloudtrail-gs/REVISION-HISTORY.md new file mode 100644 index 00000000..882e6075 --- /dev/null +++ b/tuts/094-aws-cloudtrail-gs/REVISION-HISTORY.md @@ -0,0 +1,8 @@ +# Revision History: 094-aws-cloudtrail-gs + +## Shell (CLI script) + +### 2026-04-14 v1 published +- Type: functional +- Initial version + diff --git a/tuts/094-aws-cloudtrail-gs/aws-cloudtrail-gs.md b/tuts/094-aws-cloudtrail-gs/aws-cloudtrail-gs.md new file mode 100644 index 00000000..ad949d6c --- /dev/null +++ b/tuts/094-aws-cloudtrail-gs/aws-cloudtrail-gs.md @@ -0,0 +1,117 @@ +# Enable CloudTrail logging and look up recent events + +This tutorial shows you how to create an AWS CloudTrail trail that logs API activity to an S3 bucket, look up recent events, and clean up. + +## Prerequisites + +- AWS CLI configured with credentials and a default region +- Permissions for `cloudtrail:CreateTrail`, `cloudtrail:StartLogging`, `cloudtrail:LookupEvents`, `cloudtrail:DescribeTrails`, `cloudtrail:DeleteTrail`, `s3:CreateBucket`, `s3:PutBucketPolicy`, `s3:DeleteBucket` + +## Step 1: Create an S3 bucket for trail logs + +CloudTrail delivers log files to an S3 bucket. Create a bucket with a unique name: + +```bash +BUCKET_NAME="cloudtrail-tut-$(openssl rand -hex 4)-$(aws sts get-caller-identity --query Account --output text)" + +aws s3api create-bucket --bucket "$BUCKET_NAME" \ + --create-bucket-configuration LocationConstraint="$AWS_DEFAULT_REGION" +``` + +For `us-east-1`, omit the `--create-bucket-configuration` parameter. + +## Step 2: Set the bucket policy for CloudTrail + +CloudTrail requires a bucket policy that grants it permission to check the bucket ACL and write log files: + +```bash +ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + +cat > bucket-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AWSCloudTrailAclCheck", + "Effect": "Allow", + "Principal": {"Service": "cloudtrail.amazonaws.com"}, + "Action": "s3:GetBucketAcl", + "Resource": "arn:aws:s3:::$BUCKET_NAME" + }, + { + "Sid": "AWSCloudTrailWrite", + "Effect": "Allow", + "Principal": {"Service": "cloudtrail.amazonaws.com"}, + "Action": "s3:PutObject", + "Resource": "arn:aws:s3:::$BUCKET_NAME/AWSLogs/$ACCOUNT_ID/*", + "Condition": {"StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}} + } + ] +} +EOF + +aws s3api put-bucket-policy --bucket "$BUCKET_NAME" --policy file://bucket-policy.json +``` + +The first statement lets CloudTrail verify bucket ownership. The second lets it write log files under the `AWSLogs/` prefix. + +## Step 3: Create a trail + +Create a trail that points to your S3 bucket: + +```bash +TRAIL_NAME="tutorial-trail-$(openssl rand -hex 4)" + +aws cloudtrail create-trail --name "$TRAIL_NAME" --s3-bucket-name "$BUCKET_NAME" \ + --query '{Trail:Name,Bucket:S3BucketName}' --output table +``` + +The trail is created but not yet logging. By default it records management events in all Regions. + +## Step 4: Start logging + +```bash +aws cloudtrail start-logging --name "$TRAIL_NAME" +``` + +CloudTrail begins recording API activity and delivering log files to the S3 bucket within about 15 minutes. + +## Step 5: Look up recent events + +Use `lookup-events` to search the last 10 minutes of API activity: + +```bash +START_TIME=$(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ + || date -u -v-10M +%Y-%m-%dT%H:%M:%SZ) + +aws cloudtrail lookup-events \ + --start-time "$START_TIME" \ + --max-results 5 \ + --query 'Events[].{Time:EventTime,Name:EventName,User:Username}' --output table +``` + +This returns events from the Event history, which is available regardless of whether a trail exists. The trail you created delivers the same events (plus more detail) to S3. + +## Step 6: Describe the trail + +```bash +aws cloudtrail describe-trails --trail-name-list "$TRAIL_NAME" \ + --query 'trailList[0].{Name:Name,Bucket:S3BucketName,IsMultiRegion:IsMultiRegionTrail}' \ + --output table +``` + +## Cleanup + +Delete the trail, then empty and delete the S3 bucket: + +```bash +aws cloudtrail delete-trail --name "$TRAIL_NAME" +aws s3 rm "s3://$BUCKET_NAME" --recursive --quiet +aws s3 rb "s3://$BUCKET_NAME" +``` + +The script automates all steps including cleanup. Run it with: + +```bash +bash aws-cloudtrail-gs.sh +``` diff --git a/tuts/094-aws-cloudtrail-gs/aws-cloudtrail-gs.sh b/tuts/094-aws-cloudtrail-gs/aws-cloudtrail-gs.sh new file mode 100644 index 00000000..195f28ea --- /dev/null +++ b/tuts/094-aws-cloudtrail-gs/aws-cloudtrail-gs.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# Tutorial: Enable CloudTrail logging and look up recent events +# Source: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-tutorial.html + +WORK_DIR=$(mktemp -d) +LOG_FILE="$WORK_DIR/cloudtrail-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}} +if [ -z "$REGION" ]; then + echo "ERROR: No AWS region configured. Set one with: export AWS_DEFAULT_REGION=us-east-1" + exit 1 +fi +export AWS_DEFAULT_REGION="$REGION" +ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) +echo "Region: $REGION" +echo "Account: $ACCOUNT_ID" + +RANDOM_ID=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) +TRAIL_NAME="tutorial-trail-${RANDOM_ID}" +BUCKET_NAME="cloudtrail-tut-${RANDOM_ID}-${ACCOUNT_ID}" + +handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; } +trap 'handle_error $LINENO' ERR + +cleanup() { + echo "" + echo "Cleaning up resources..." + aws cloudtrail delete-trail --name "$TRAIL_NAME" 2>/dev/null && echo " Deleted trail $TRAIL_NAME" + # Empty and delete the bucket + if aws s3 ls "s3://$BUCKET_NAME" > /dev/null 2>&1; then + aws s3 rm "s3://$BUCKET_NAME" --recursive --quiet 2>/dev/null + aws s3 rb "s3://$BUCKET_NAME" 2>/dev/null && echo " Deleted bucket $BUCKET_NAME" + fi + rm -rf "$WORK_DIR" + echo "Cleanup complete." +} + +# Step 1: Create S3 bucket for trail logs +echo "Step 1: Creating S3 bucket for trail logs: $BUCKET_NAME" +if [ "$REGION" = "us-east-1" ]; then + aws s3api create-bucket --bucket "$BUCKET_NAME" > /dev/null +else + aws s3api create-bucket --bucket "$BUCKET_NAME" \ + --create-bucket-configuration LocationConstraint="$REGION" > /dev/null +fi + +# Step 2: Set bucket policy to allow CloudTrail writes +echo "Step 2: Setting bucket policy for CloudTrail" +cat > "$WORK_DIR/bucket-policy.json" << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AWSCloudTrailAclCheck", + "Effect": "Allow", + "Principal": {"Service": "cloudtrail.amazonaws.com"}, + "Action": "s3:GetBucketAcl", + "Resource": "arn:aws:s3:::$BUCKET_NAME" + }, + { + "Sid": "AWSCloudTrailWrite", + "Effect": "Allow", + "Principal": {"Service": "cloudtrail.amazonaws.com"}, + "Action": "s3:PutObject", + "Resource": "arn:aws:s3:::$BUCKET_NAME/AWSLogs/$ACCOUNT_ID/*", + "Condition": {"StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}} + } + ] +} +EOF +aws s3api put-bucket-policy --bucket "$BUCKET_NAME" --policy "file://$WORK_DIR/bucket-policy.json" +echo " Bucket policy applied" + +# Step 3: Create a trail +echo "Step 3: Creating trail: $TRAIL_NAME" +aws cloudtrail create-trail --name "$TRAIL_NAME" --s3-bucket-name "$BUCKET_NAME" \ + --query '{Trail:Name,Bucket:S3BucketName}' --output table + +# Step 4: Start logging +echo "Step 4: Starting logging" +aws cloudtrail start-logging --name "$TRAIL_NAME" +echo " Logging started" + +# Step 5: Look up recent events +echo "Step 5: Looking up recent API events (last 10 minutes)" +START_TIME=$(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-10M +%Y-%m-%dT%H:%M:%SZ) +aws cloudtrail lookup-events \ + --start-time "$START_TIME" \ + --max-results 5 \ + --query 'Events[].{Time:EventTime,Name:EventName,User:Username}' --output table + +# Step 6: Describe the trail +echo "Step 6: Describing the trail" +aws cloudtrail describe-trails --trail-name-list "$TRAIL_NAME" \ + --query 'trailList[0].{Name:Name,Bucket:S3BucketName,IsMultiRegion:IsMultiRegionTrail,IsLogging:HasCustomEventSelectors}' --output table + +echo "" +echo "Tutorial complete." +echo "Do you want to clean up all resources? (y/n): " +read -r CHOICE +if [[ "$CHOICE" =~ ^[Yy]$ ]]; then + cleanup +else + echo "Resources left running. The trail logs API activity to S3." + echo "Manual cleanup:" + echo " aws cloudtrail delete-trail --name $TRAIL_NAME" + echo " aws s3 rm s3://$BUCKET_NAME --recursive" + echo " aws s3 rb s3://$BUCKET_NAME" +fi diff --git a/tuts/099-amazon-cloudwatch-logs-gs/README.md b/tuts/099-amazon-cloudwatch-logs-gs/README.md new file mode 100644 index 00000000..cc903b41 --- /dev/null +++ b/tuts/099-amazon-cloudwatch-logs-gs/README.md @@ -0,0 +1,55 @@ +# CloudWatch Logs: Create log groups and query logs + +## Source + +https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html + +## Use case + +- **ID**: cloudwatch-logs/getting-started +- **Level**: beginner +- **Core actions**: `logs:CreateLogGroup`, `logs:PutLogEvents`, `logs:FilterLogEvents`, `logs:StartQuery` + +## Steps + +1. Create a log group +2. Set retention to 7 days +3. Create a log stream +4. Put 5 log events (INFO/WARN/ERROR) +5. Get log events +6. Filter for ERROR and WARN +7. Run a Logs Insights query + +## Resources created + +| Resource | Type | +|----------|------| +| `/tutorials/cloudwatch-logs-gs` | Log group | + +## Cost + +Negligible for 5 log events. All resources removed during cleanup. + +## Duration + +~14 seconds + +## Related docs + +- [What is Amazon CloudWatch Logs?](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html) +- [CloudWatch Logs Insights query syntax](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html) +- [Filter and pattern syntax](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html) + +--- + +## Appendix + +| Field | Value | +|-------|-------| +| Date | 2026-04-14 | +| Script lines | 91 | +| Exit code | 0 | +| Runtime | 14s | +| Steps | 7 | +| Issues | None | +| Version | v1 | diff --git a/tuts/099-amazon-cloudwatch-logs-gs/REVISION-HISTORY.md b/tuts/099-amazon-cloudwatch-logs-gs/REVISION-HISTORY.md new file mode 100644 index 00000000..90f56bd0 --- /dev/null +++ b/tuts/099-amazon-cloudwatch-logs-gs/REVISION-HISTORY.md @@ -0,0 +1,8 @@ +# Revision History: 099-amazon-cloudwatch-logs-gs + +## Shell (CLI script) + +### 2026-04-14 v1 published +- Type: functional +- Initial version + diff --git a/tuts/099-amazon-cloudwatch-logs-gs/amazon-cloudwatch-logs-gs.md b/tuts/099-amazon-cloudwatch-logs-gs/amazon-cloudwatch-logs-gs.md new file mode 100644 index 00000000..d0df81cc --- /dev/null +++ b/tuts/099-amazon-cloudwatch-logs-gs/amazon-cloudwatch-logs-gs.md @@ -0,0 +1,110 @@ +# CloudWatch Logs: Create log groups and query logs + +## Overview + +In this tutorial, you use the AWS CLI to create a CloudWatch Logs log group, send log events to it, and query the logs using both filter patterns and Logs Insights. + +## Prerequisites + +- AWS CLI installed and configured with appropriate permissions. +- An IAM principal with permissions for `logs:CreateLogGroup`, `logs:PutRetentionPolicy`, `logs:CreateLogStream`, `logs:PutLogEvents`, `logs:GetLogEvents`, `logs:FilterLogEvents`, `logs:StartQuery`, `logs:GetQueryResults`, and `logs:DeleteLogGroup`. + +## Step 1: Create a log group + +Create a log group to store your log events. + +```bash +aws logs create-log-group \ + --log-group-name /tutorials/cloudwatch-logs-gs +``` + +## Step 2: Set retention to 7 days + +Configure the log group to automatically expire log data after 7 days. + +```bash +aws logs put-retention-policy \ + --log-group-name /tutorials/cloudwatch-logs-gs \ + --retention-in-days 7 +``` + +## Step 3: Create a log stream + +Create a log stream within the log group. + +```bash +aws logs create-log-stream \ + --log-group-name /tutorials/cloudwatch-logs-gs \ + --log-stream-name app-stream +``` + +## Step 4: Put log events + +Send five log events with a mix of INFO, WARN, and ERROR levels. + +```bash +NOW=$(date +%s000) + +aws logs put-log-events \ + --log-group-name /tutorials/cloudwatch-logs-gs \ + --log-stream-name app-stream \ + --log-events \ + timestamp=$NOW,message="[INFO] Application started successfully" \ + timestamp=$((NOW+1)),message="[INFO] Processing request batch" \ + timestamp=$((NOW+2)),message="[WARN] High memory usage detected" \ + timestamp=$((NOW+3)),message="[ERROR] Failed to connect to database" \ + timestamp=$((NOW+4)),message="[ERROR] Request timeout after 30s" +``` + +## Step 5: Get log events + +Retrieve the log events from the stream. + +```bash +aws logs get-log-events \ + --log-group-name /tutorials/cloudwatch-logs-gs \ + --log-stream-name app-stream +``` + +## Step 6: Filter for ERROR and WARN + +Use a filter pattern to find only ERROR and WARN messages. + +```bash +aws logs filter-log-events \ + --log-group-name /tutorials/cloudwatch-logs-gs \ + --filter-pattern "?ERROR ?WARN" +``` + +## Step 7: Run a Logs Insights query + +Run a Logs Insights query to parse and count log events by level. + +```bash +QUERY_ID=$(aws logs start-query \ + --log-group-name /tutorials/cloudwatch-logs-gs \ + --start-time $(date -d '1 hour ago' +%s) \ + --end-time $(date +%s) \ + --query-string 'fields @timestamp, @message | parse @message "[*] *" as level, msg | stats count(*) by level' \ + --output text --query 'queryId') + +sleep 5 + +aws logs get-query-results \ + --query-id "$QUERY_ID" +``` + +## Cleanup + +Delete the log group to remove all associated log streams and log data. + +```bash +aws logs delete-log-group \ + --log-group-name /tutorials/cloudwatch-logs-gs +``` + +## Related resources + +- [What is Amazon CloudWatch Logs?](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html) +- [CloudWatch Logs Insights query syntax](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html) +- [Filter and pattern syntax](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html) diff --git a/tuts/099-amazon-cloudwatch-logs-gs/amazon-cloudwatch-logs-gs.sh b/tuts/099-amazon-cloudwatch-logs-gs/amazon-cloudwatch-logs-gs.sh new file mode 100644 index 00000000..f4b4b0ec --- /dev/null +++ b/tuts/099-amazon-cloudwatch-logs-gs/amazon-cloudwatch-logs-gs.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# Tutorial: Create log groups, streams, and query logs with CloudWatch Logs +# Source: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html + +WORK_DIR=$(mktemp -d) +LOG_FILE="$WORK_DIR/cwlogs-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}} +if [ -z "$REGION" ]; then + echo "ERROR: No AWS region configured. Set one with: export AWS_DEFAULT_REGION=us-east-1" + exit 1 +fi +export AWS_DEFAULT_REGION="$REGION" +echo "Region: $REGION" + +RANDOM_ID=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) +LOG_GROUP="/tutorial/app-${RANDOM_ID}" +STREAM_NAME="web-server" + +handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; } +trap 'handle_error $LINENO' ERR + +cleanup() { + echo "" + echo "Cleaning up resources..." + aws logs delete-log-group --log-group-name "$LOG_GROUP" 2>/dev/null && \ + echo " Deleted log group $LOG_GROUP" + rm -rf "$WORK_DIR" + echo "Cleanup complete." +} + +# Step 1: Create a log group +echo "Step 1: Creating log group: $LOG_GROUP" +aws logs create-log-group --log-group-name "$LOG_GROUP" +echo " Log group created" + +# Step 2: Set retention policy +echo "Step 2: Setting retention to 7 days" +aws logs put-retention-policy --log-group-name "$LOG_GROUP" --retention-in-days 7 +aws logs describe-log-groups --log-group-name-prefix "$LOG_GROUP" \ + --query 'logGroups[0].{Name:logGroupName,Retention:retentionInDays}' --output table + +# Step 3: Create a log stream +echo "Step 3: Creating log stream: $STREAM_NAME" +aws logs create-log-stream --log-group-name "$LOG_GROUP" --log-stream-name "$STREAM_NAME" + +# Step 4: Put log events +echo "Step 4: Writing log events" +NOW_MS=$(($(date +%s) * 1000)) +aws logs put-log-events --log-group-name "$LOG_GROUP" --log-stream-name "$STREAM_NAME" \ + --log-events \ + "[{\"timestamp\":$NOW_MS,\"message\":\"INFO: Application started on port 8080\"},{\"timestamp\":$((NOW_MS+1000)),\"message\":\"INFO: Connected to database\"},{\"timestamp\":$((NOW_MS+2000)),\"message\":\"WARN: Slow query detected (1200ms)\"},{\"timestamp\":$((NOW_MS+3000)),\"message\":\"ERROR: Connection timeout to upstream service\"},{\"timestamp\":$((NOW_MS+4000)),\"message\":\"INFO: Request processed in 45ms\"}]" \ + > /dev/null +echo " Wrote 5 log events" + +# Step 5: Get log events +echo "Step 5: Retrieving log events" +sleep 2 +aws logs get-log-events --log-group-name "$LOG_GROUP" --log-stream-name "$STREAM_NAME" \ + --query 'events[].{Time:timestamp,Message:message}' --output table + +# Step 6: Filter log events +echo "Step 6: Filtering for ERROR and WARN messages" +aws logs filter-log-events --log-group-name "$LOG_GROUP" \ + --filter-pattern "?ERROR ?WARN" \ + --query 'events[].{Message:message}' --output table + +# Step 7: Run a Logs Insights query +echo "Step 7: Running Logs Insights query" +QUERY_ID=$(aws logs start-query \ + --log-group-name "$LOG_GROUP" \ + --start-time $(($(date +%s) - 300)) \ + --end-time $(date +%s) \ + --query-string 'fields @timestamp, @message | filter @message like /ERROR|WARN/ | sort @timestamp desc' \ + --query 'queryId' --output text) +echo " Query ID: $QUERY_ID" +sleep 3 +aws logs get-query-results --query-id "$QUERY_ID" \ + --query '{Status:status,Results:results[].{message:@[?field==`@message`].value|[0]}}' --output table + +echo "" +echo "Tutorial complete." +echo "Do you want to clean up all resources? (y/n): " +read -r CHOICE +if [[ "$CHOICE" =~ ^[Yy]$ ]]; then + cleanup +else + echo "Manual cleanup:" + echo " aws logs delete-log-group --log-group-name $LOG_GROUP" +fi diff --git a/tuts/106-aws-budgets-gs/README.md b/tuts/106-aws-budgets-gs/README.md new file mode 100644 index 00000000..7aacb81b --- /dev/null +++ b/tuts/106-aws-budgets-gs/README.md @@ -0,0 +1,38 @@ +# Budgets: Create a cost budget + +## Source + +https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-create.html + +## Use case + +- **ID**: budgets/getting-started +- **Level**: beginner +- **Core actions**: `budgets:CreateBudget`, `budgets:DescribeBudget`, `budgets:DescribeBudgets` + +## Steps + +1. Create a monthly cost budget ($100 limit) +2. Describe the budget +3. Check current spend vs budget +4. List all budgets + +## Resources created + +| Resource | Type | +|----------|------| +| `tutorial-budget-` | Budget | + +## Cost + +Free. The first two budgets per account have no charge. Additional budgets cost $0.02/day each. + +## Duration + +~5 seconds + +## Related docs + +- [Creating a cost budget](https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-create.html) +- [Managing your costs with AWS Budgets](https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-managing-costs.html) +- [AWS Budgets CLI reference](https://docs.aws.amazon.com/cli/latest/reference/budgets/index.html) diff --git a/tuts/106-aws-budgets-gs/REVISION-HISTORY.md b/tuts/106-aws-budgets-gs/REVISION-HISTORY.md new file mode 100644 index 00000000..fc9235c0 --- /dev/null +++ b/tuts/106-aws-budgets-gs/REVISION-HISTORY.md @@ -0,0 +1,8 @@ +# Revision History: 106-aws-budgets-gs + +## Shell (CLI script) + +### 2026-04-14 v1 published +- Type: functional +- Initial version + diff --git a/tuts/106-aws-budgets-gs/aws-budgets-gs.md b/tuts/106-aws-budgets-gs/aws-budgets-gs.md new file mode 100644 index 00000000..3ec0c6be --- /dev/null +++ b/tuts/106-aws-budgets-gs/aws-budgets-gs.md @@ -0,0 +1,84 @@ +# Create a cost budget with AWS Budgets + +This tutorial shows you how to create a monthly cost budget with a $100 limit, inspect the budget details and current spend, list all budgets in your account, and delete the budget. + +## Prerequisites + +- AWS CLI configured with credentials and a default region +- Permissions for `budgets:CreateBudget`, `budgets:DescribeBudget`, `budgets:DescribeBudgets`, `budgets:DeleteBudget` + +## Step 1: Create a monthly cost budget + +Create a JSON budget definition and pass it to `create-budget`. The budget tracks actual spend against a $100 monthly limit. + +```bash +ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) +BUDGET_NAME="tutorial-budget-$(openssl rand -hex 4)" + +cat > /tmp/budget.json << EOF +{ + "BudgetName": "$BUDGET_NAME", + "BudgetLimit": {"Amount": "100", "Unit": "USD"}, + "BudgetType": "COST", + "TimeUnit": "MONTHLY", + "TimePeriod": { + "Start": "2026-04-01T00:00:00Z", + "End": "2087-06-15T00:00:00Z" + } +} +EOF + +aws budgets create-budget --account-id "$ACCOUNT_ID" \ + --budget "file:///tmp/budget.json" +``` + +`BudgetType` can be `COST`, `USAGE`, `RI_UTILIZATION`, `RI_COVERAGE`, `SAVINGS_PLANS_UTILIZATION`, or `SAVINGS_PLANS_COVERAGE`. This tutorial uses `COST` to track dollar spend. + +## Step 2: Describe the budget + +```bash +aws budgets describe-budget --account-id "$ACCOUNT_ID" \ + --budget-name "$BUDGET_NAME" \ + --query 'Budget.{Name:BudgetName,Limit:BudgetLimit.Amount,Type:BudgetType,TimeUnit:TimeUnit}' \ + --output table +``` + +## Step 3: Check current spend vs budget + +```bash +aws budgets describe-budget --account-id "$ACCOUNT_ID" \ + --budget-name "$BUDGET_NAME" \ + --query 'Budget.CalculatedSpend.{ActualSpend:ActualSpend.Amount,ForecastedSpend:ForecastedSpend.Amount}' \ + --output table +``` + +`CalculatedSpend` shows actual spend so far in the current period and the forecasted spend by period end. A new budget shows zero for both values. + +## Step 4: List all budgets + +```bash +aws budgets describe-budgets --account-id "$ACCOUNT_ID" \ + --query 'Budgets[].{Name:BudgetName,Limit:BudgetLimit.Amount,Type:BudgetType}' \ + --output table +``` + +## Cleanup + +Delete the budget: + +```bash +aws budgets delete-budget --account-id "$ACCOUNT_ID" \ + --budget-name "$BUDGET_NAME" +``` + +The first two budgets per account are free. Deleting the budget removes it immediately. The script automates all steps including cleanup: + +```bash +bash aws-budgets-gs.sh +``` + +## Related resources + +- [Creating a cost budget](https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-create.html) +- [Managing your costs with AWS Budgets](https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-managing-costs.html) +- [AWS Budgets CLI reference](https://docs.aws.amazon.com/cli/latest/reference/budgets/index.html) diff --git a/tuts/106-aws-budgets-gs/aws-budgets-gs.sh b/tuts/106-aws-budgets-gs/aws-budgets-gs.sh new file mode 100644 index 00000000..885b13ef --- /dev/null +++ b/tuts/106-aws-budgets-gs/aws-budgets-gs.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# Tutorial: Create a cost budget with AWS Budgets +# Source: https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-create.html + +WORK_DIR=$(mktemp -d) +LOG_FILE="$WORK_DIR/budgets-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}} +if [ -z "$REGION" ]; then + echo "ERROR: No AWS region configured. Set one with: export AWS_DEFAULT_REGION=us-east-1" + exit 1 +fi +export AWS_DEFAULT_REGION="$REGION" +ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) +echo "Region: $REGION" +echo "Account: $ACCOUNT_ID" + +RANDOM_ID=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) +BUDGET_NAME="tutorial-budget-${RANDOM_ID}" + +handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; } +trap 'handle_error $LINENO' ERR + +cleanup() { + echo "" + echo "Cleaning up resources..." + aws budgets delete-budget --account-id "$ACCOUNT_ID" --budget-name "$BUDGET_NAME" 2>/dev/null && \ + echo " Deleted budget $BUDGET_NAME" + rm -rf "$WORK_DIR" + echo "Cleanup complete." +} + +# Step 1: Create a monthly cost budget +echo "Step 1: Creating monthly cost budget: $BUDGET_NAME (\$100 limit)" +cat > "$WORK_DIR/budget.json" << EOF +{ + "BudgetName": "$BUDGET_NAME", + "BudgetLimit": {"Amount": "100", "Unit": "USD"}, + "BudgetType": "COST", + "TimeUnit": "MONTHLY", + "TimePeriod": { + "Start": "2026-04-01T00:00:00Z", + "End": "2087-06-15T00:00:00Z" + } +} +EOF +aws budgets create-budget --account-id "$ACCOUNT_ID" \ + --budget "file://$WORK_DIR/budget.json" +echo " Budget created" + +# Step 2: Describe the budget +echo "Step 2: Describing the budget" +aws budgets describe-budget --account-id "$ACCOUNT_ID" --budget-name "$BUDGET_NAME" \ + --query 'Budget.{Name:BudgetName,Limit:BudgetLimit.Amount,Type:BudgetType,TimeUnit:TimeUnit}' --output table + +# Step 3: Check current spend vs budget +echo "Step 3: Current spend vs budget" +aws budgets describe-budget --account-id "$ACCOUNT_ID" --budget-name "$BUDGET_NAME" \ + --query 'Budget.CalculatedSpend.{ActualSpend:ActualSpend.Amount,ForecastedSpend:ForecastedSpend.Amount}' --output table + +# Step 4: List all budgets +echo "Step 4: Listing all budgets" +aws budgets describe-budgets --account-id "$ACCOUNT_ID" \ + --query 'Budgets[].{Name:BudgetName,Limit:BudgetLimit.Amount,Type:BudgetType}' --output table + +echo "" +echo "Tutorial complete." +echo "Do you want to clean up all resources? (y/n): " +read -r CHOICE +if [[ "$CHOICE" =~ ^[Yy]$ ]]; then + cleanup +else + echo "Manual cleanup:" + echo " aws budgets delete-budget --account-id $ACCOUNT_ID --budget-name $BUDGET_NAME" +fi diff --git a/tuts/111-amazon-cloudwatch-gs/README.md b/tuts/111-amazon-cloudwatch-gs/README.md new file mode 100644 index 00000000..305b9456 --- /dev/null +++ b/tuts/111-amazon-cloudwatch-gs/README.md @@ -0,0 +1,57 @@ +# CloudWatch: Create alarms and dashboards + +## Source + +https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/GettingStarted.html + +## Use case + +- **ID**: cloudwatch/getting-started +- **Level**: beginner +- **Core actions**: `cloudwatch:PutMetricData`, `cloudwatch:PutMetricAlarm` + +## Steps + +1. Publish custom metrics +2. Retrieve metric statistics +3. Create an alarm +4. Describe the alarm +5. Create a dashboard +6. List dashboards + +## Resources created + +| Resource | Type | +|----------|------| +| `tut-alarm-` | CloudWatch alarm | +| `tut-dashboard-` | CloudWatch dashboard | +| `Tutorial/App` | Custom metric namespace | + +## Cost + +Free tier includes 10 alarms and 3 dashboards. This tutorial creates 1 alarm and 1 dashboard. + +## Duration + +~12 seconds + +## Related docs + +- [Getting started with CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/GettingStarted.html) +- [Using Amazon CloudWatch alarms](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html) +- [Using CloudWatch dashboards](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html) +- [Publishing custom metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/publishingMetrics.html) + +--- + +## Appendix + +| Field | Value | +|-------|-------| +| Date | 2026-04-14 | +| Script lines | 99 | +| Exit code | 0 | +| Runtime | 12s | +| Steps | 6 | +| Issues | None | +| Version | v1 | diff --git a/tuts/111-amazon-cloudwatch-gs/REVISION-HISTORY.md b/tuts/111-amazon-cloudwatch-gs/REVISION-HISTORY.md new file mode 100644 index 00000000..e0733dcc --- /dev/null +++ b/tuts/111-amazon-cloudwatch-gs/REVISION-HISTORY.md @@ -0,0 +1,8 @@ +# Revision History: 111-amazon-cloudwatch-gs + +## Shell (CLI script) + +### 2026-04-14 v1 published +- Type: functional +- Initial version + diff --git a/tuts/111-amazon-cloudwatch-gs/amazon-cloudwatch-gs.md b/tuts/111-amazon-cloudwatch-gs/amazon-cloudwatch-gs.md new file mode 100644 index 00000000..9c882faf --- /dev/null +++ b/tuts/111-amazon-cloudwatch-gs/amazon-cloudwatch-gs.md @@ -0,0 +1,125 @@ +# Create alarms and dashboards with Amazon CloudWatch + +## Overview + +In this tutorial, you use the AWS CLI to publish custom metrics, retrieve statistics, create an alarm, and build a dashboard with metric widgets. You then clean up all resources. + +## Prerequisites + +- AWS CLI installed and configured with appropriate permissions. +- An IAM principal with permissions for `cloudwatch:PutMetricData`, `cloudwatch:GetMetricStatistics`, `cloudwatch:PutMetricAlarm`, `cloudwatch:DescribeAlarms`, `cloudwatch:DeleteAlarms`, `cloudwatch:PutDashboard`, `cloudwatch:ListDashboards`, and `cloudwatch:DeleteDashboards`. + +## Step 1: Publish custom metrics + +Publish five data points to a custom namespace. + +```bash +RANDOM_ID=$(openssl rand -hex 4) + +for i in $(seq 1 5); do + VALUE=$((RANDOM % 100)) + aws cloudwatch put-metric-data --namespace "Tutorial/App" \ + --metric-name RequestLatency --value "$VALUE" --unit Milliseconds +done +``` + +Custom metrics appear in the `Tutorial/App` namespace. Each `put-metric-data` call can include up to 1,000 data points. + +## Step 2: Retrieve metric statistics + +Query the average, maximum, and minimum values over the last 5 minutes. + +```bash +aws cloudwatch get-metric-statistics \ + --namespace "Tutorial/App" --metric-name RequestLatency \ + --start-time "$(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%SZ)" \ + --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --period 60 --statistics Average Maximum Minimum \ + --query 'Datapoints[0].{Avg:Average,Max:Maximum,Min:Minimum}' --output table +``` + +Newly published metrics can take 1–2 minutes to appear in statistics queries. + +## Step 3: Create an alarm + +Create an alarm that triggers when average latency exceeds 80 milliseconds. + +```bash +ALARM_NAME="tut-alarm-${RANDOM_ID}" + +aws cloudwatch put-metric-alarm \ + --alarm-name "$ALARM_NAME" \ + --namespace "Tutorial/App" --metric-name RequestLatency \ + --statistic Average --period 60 \ + --threshold 80 --comparison-operator GreaterThanThreshold \ + --evaluation-periods 1 \ + --alarm-description "Tutorial alarm: latency > 80ms" +``` + +The alarm evaluates one 60-second period. Add `--alarm-actions` with an SNS topic ARN to send notifications when the alarm triggers. + +## Step 4: Describe the alarm + +View the alarm's current state and configuration. + +```bash +aws cloudwatch describe-alarms --alarm-names "$ALARM_NAME" \ + --query 'MetricAlarms[0].{Name:AlarmName,State:StateValue,Threshold:Threshold,Metric:MetricName}' \ + --output table +``` + +New alarms start in `INSUFFICIENT_DATA` state until enough data points are collected for evaluation. + +## Step 5: Create a dashboard + +Create a dashboard with two metric widgets showing average and maximum latency. + +```bash +DASHBOARD_NAME="tut-dashboard-${RANDOM_ID}" +REGION=$(aws configure get region) + +aws cloudwatch put-dashboard --dashboard-name "$DASHBOARD_NAME" \ + --dashboard-body "{ + \"widgets\": [ + {\"type\":\"metric\",\"x\":0,\"y\":0,\"width\":12,\"height\":6, + \"properties\":{\"metrics\":[[\"Tutorial/App\",\"RequestLatency\"]], + \"period\":60,\"stat\":\"Average\",\"region\":\"$REGION\",\"title\":\"Request Latency\"}}, + {\"type\":\"metric\",\"x\":12,\"y\":0,\"width\":12,\"height\":6, + \"properties\":{\"metrics\":[[\"Tutorial/App\",\"RequestLatency\"]], + \"period\":60,\"stat\":\"Maximum\",\"region\":\"$REGION\",\"title\":\"Max Latency\"}} + ] + }" +``` + +Dashboard widgets reference metrics by namespace and metric name. The dashboard updates automatically as new data points arrive. + +## Step 6: List dashboards + +List dashboards matching the tutorial prefix. + +```bash +aws cloudwatch list-dashboards --dashboard-name-prefix "tut-" \ + --query 'DashboardEntries[].{Name:DashboardName,Size:Size}' --output table +``` + +## Cleanup + +Delete the alarm and dashboard. Custom metric data expires automatically based on its age (high-resolution data after 3 hours, 1-minute data after 15 days). + +```bash +aws cloudwatch delete-alarms --alarm-names "$ALARM_NAME" +aws cloudwatch delete-dashboards --dashboard-names "$DASHBOARD_NAME" +``` + +The script automates all steps including cleanup: + +```bash +bash amazon-cloudwatch-gs.sh +``` + +## Related resources + +- [Getting started with CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/GettingStarted.html) +- [Using Amazon CloudWatch alarms](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html) +- [Using CloudWatch dashboards](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html) +- [Publishing custom metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/publishingMetrics.html) diff --git a/tuts/111-amazon-cloudwatch-gs/amazon-cloudwatch-gs.sh b/tuts/111-amazon-cloudwatch-gs/amazon-cloudwatch-gs.sh new file mode 100644 index 00000000..fbd13af4 --- /dev/null +++ b/tuts/111-amazon-cloudwatch-gs/amazon-cloudwatch-gs.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# Tutorial: Create CloudWatch alarms and dashboards +# Source: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/GettingStarted.html + +WORK_DIR=$(mktemp -d) +LOG_FILE="$WORK_DIR/cloudwatch-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}} +if [ -z "$REGION" ]; then + echo "ERROR: No AWS region configured. Set one with: export AWS_DEFAULT_REGION=us-east-1" + exit 1 +fi +export AWS_DEFAULT_REGION="$REGION" +echo "Region: $REGION" + +RANDOM_ID=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) +ALARM_NAME="tut-alarm-${RANDOM_ID}" +DASHBOARD_NAME="tut-dashboard-${RANDOM_ID}" + +handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; } +trap 'handle_error $LINENO' ERR + +cleanup() { + echo "" + echo "Cleaning up resources..." + aws cloudwatch delete-alarms --alarm-names "$ALARM_NAME" 2>/dev/null && echo " Deleted alarm $ALARM_NAME" + aws cloudwatch delete-dashboards --dashboard-names "$DASHBOARD_NAME" 2>/dev/null && echo " Deleted dashboard $DASHBOARD_NAME" + rm -rf "$WORK_DIR" + echo "Cleanup complete." +} + +# Step 1: Put custom metrics +echo "Step 1: Publishing custom metrics" +for i in $(seq 1 5); do + VALUE=$((RANDOM % 100)) + aws cloudwatch put-metric-data --namespace "Tutorial/App" \ + --metric-name RequestLatency --value "$VALUE" --unit Milliseconds +done +echo " Published 5 data points to Tutorial/App namespace" + +# Step 2: Get metric statistics +echo "Step 2: Retrieving metric statistics" +sleep 2 +aws cloudwatch get-metric-statistics \ + --namespace "Tutorial/App" --metric-name RequestLatency \ + --start-time "$(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%SZ)" \ + --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --period 60 --statistics Average Maximum Minimum \ + --query 'Datapoints[0].{Avg:Average,Max:Maximum,Min:Minimum}' --output table 2>/dev/null || \ + echo " Metrics not yet available (can take 1-2 minutes)" + +# Step 3: Create an alarm +echo "Step 3: Creating alarm: $ALARM_NAME" +aws cloudwatch put-metric-alarm \ + --alarm-name "$ALARM_NAME" \ + --namespace "Tutorial/App" --metric-name RequestLatency \ + --statistic Average --period 60 \ + --threshold 80 --comparison-operator GreaterThanThreshold \ + --evaluation-periods 1 \ + --alarm-description "Tutorial alarm: latency > 80ms" +echo " Alarm created (triggers when avg latency > 80ms)" + +# Step 4: Describe the alarm +echo "Step 4: Alarm details" +aws cloudwatch describe-alarms --alarm-names "$ALARM_NAME" \ + --query 'MetricAlarms[0].{Name:AlarmName,State:StateValue,Threshold:Threshold,Metric:MetricName}' --output table + +# Step 5: Create a dashboard +echo "Step 5: Creating dashboard: $DASHBOARD_NAME" +aws cloudwatch put-dashboard --dashboard-name "$DASHBOARD_NAME" \ + --dashboard-body "{ + \"widgets\": [ + {\"type\":\"metric\",\"x\":0,\"y\":0,\"width\":12,\"height\":6, + \"properties\":{\"metrics\":[[\"Tutorial/App\",\"RequestLatency\"]], + \"period\":60,\"stat\":\"Average\",\"region\":\"$REGION\",\"title\":\"Request Latency\"}}, + {\"type\":\"metric\",\"x\":12,\"y\":0,\"width\":12,\"height\":6, + \"properties\":{\"metrics\":[[\"Tutorial/App\",\"RequestLatency\"]], + \"period\":60,\"stat\":\"Maximum\",\"region\":\"$REGION\",\"title\":\"Max Latency\"}} + ] + }" > /dev/null +echo " Dashboard created with 2 widgets" + +# Step 6: List dashboards +echo "Step 6: Listing dashboards" +aws cloudwatch list-dashboards --dashboard-name-prefix "tut-" \ + --query 'DashboardEntries[].{Name:DashboardName,Size:Size}' --output table + +echo "" +echo "Tutorial complete." +echo "Do you want to clean up all resources? (y/n): " +read -r CHOICE +if [[ "$CHOICE" =~ ^[Yy]$ ]]; then + cleanup +else + echo "Manual cleanup:" + echo " aws cloudwatch delete-alarms --alarm-names $ALARM_NAME" + echo " aws cloudwatch delete-dashboards --dashboard-names $DASHBOARD_NAME" +fi diff --git a/tuts/113-aws-config-gs/README.md b/tuts/113-aws-config-gs/README.md new file mode 100644 index 00000000..2cda62ee --- /dev/null +++ b/tuts/113-aws-config-gs/README.md @@ -0,0 +1,59 @@ +# Config: Add a rule and check compliance + +## Source + +https://docs.aws.amazon.com/config/latest/developerguide/getting-started.html + +## Use case + +- **ID**: config/getting-started +- **Level**: intermediate +- **Core actions**: `configservice:PutConfigRule`, `configservice:GetComplianceDetailsByConfigRule` + +## Prerequisites + +An existing AWS Config recorder must be enabled in your account. Only one recorder is allowed per account per Region. The tutorial checks for a running recorder and exits if none is found. + +## Steps + +1. Check Config recorder status +2. List discovered S3 buckets +3. Add a managed rule (S3 encryption check) +4. Trigger rule evaluation +5. Check compliance details +6. View compliance summary + +## Resources created + +| Resource | Type | +|----------|------| +| `tut-s3-encryption-` | Config rule | + +## Cost + +No additional cost for managed Config rules if you already have a recorder running. Config charges per rule evaluation ($0.001 per evaluation in most Regions). The rule is deleted during cleanup. + +## Duration + +~36 seconds (includes 30-second wait for rule evaluation) + +## Related docs + +- [Getting started with AWS Config](https://docs.aws.amazon.com/config/latest/developerguide/getting-started.html) +- [AWS Config managed rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html) +- [Evaluating resources](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config.html) +- [AWS Config pricing](https://aws.amazon.com/config/pricing/) + +--- + +## Appendix + +| Field | Value | +|-------|-------| +| Date | 2026-04-14 | +| Script lines | 83 | +| Exit code | 0 | +| Runtime | 36s | +| Steps | 6 | +| Issues | Rewritten to use existing recorder (only 1 allowed per account) | +| Version | v1 | diff --git a/tuts/113-aws-config-gs/REVISION-HISTORY.md b/tuts/113-aws-config-gs/REVISION-HISTORY.md new file mode 100644 index 00000000..e7b2fb5c --- /dev/null +++ b/tuts/113-aws-config-gs/REVISION-HISTORY.md @@ -0,0 +1,8 @@ +# Revision History: 113-aws-config-gs + +## Shell (CLI script) + +### 2026-04-14 v1 published +- Type: functional +- Initial version + diff --git a/tuts/113-aws-config-gs/aws-config-gs.md b/tuts/113-aws-config-gs/aws-config-gs.md new file mode 100644 index 00000000..668108d1 --- /dev/null +++ b/tuts/113-aws-config-gs/aws-config-gs.md @@ -0,0 +1,115 @@ +# Add a rule and check compliance with AWS Config + +## Overview + +In this tutorial, you use the AWS CLI to add a managed AWS Config rule that checks whether S3 buckets have server-side encryption enabled, trigger an evaluation, and review compliance results. The tutorial requires an existing Config recorder — it does not create one because AWS allows only one recorder per account per Region. + +## Prerequisites + +- AWS CLI installed and configured with appropriate permissions. +- An IAM principal with permissions for `config:DescribeConfigurationRecorderStatus`, `config:ListDiscoveredResources`, `config:PutConfigRule`, `config:StartConfigRulesEvaluation`, `config:GetComplianceDetailsByConfigRule`, `config:DescribeComplianceByConfigRule`, and `config:DeleteConfigRule`. +- An active AWS Config recorder in the current Region. Enable one in the [AWS Config console](https://console.aws.amazon.com/config/home) if you don't have one. + +## Step 1: Check Config recorder status + +Verify that a Config recorder is running. The tutorial exits if no recorder is found. + +```bash +RECORDER=$(aws configservice describe-configuration-recorder-status \ + --query 'ConfigurationRecordersStatus[0].{Name:name,Recording:recording}' --output table 2>/dev/null) +if [ -z "$RECORDER" ]; then + echo "No Config recorder found. Enable AWS Config in the console first." + exit 1 +fi +echo "$RECORDER" +``` + +AWS Config uses a recorder to track resource configuration changes. Each account can have only one recorder per Region, so the tutorial works with your existing recorder rather than creating a new one. + +## Step 2: List discovered resources + +List S3 buckets that Config has discovered and is tracking. + +```bash +aws configservice list-discovered-resources --resource-type AWS::S3::Bucket \ + --query 'resourceIdentifiers[:5].{Type:resourceType,Id:resourceId}' --output table +``` + +Config discovers resources based on the resource types your recorder is configured to track. If you see no results, your recorder may not be tracking S3 buckets. + +## Step 3: Add a managed rule + +Add a managed rule that checks whether S3 buckets have server-side encryption enabled. + +```bash +RANDOM_ID=$(openssl rand -hex 4) +RULE_NAME="tut-s3-encryption-${RANDOM_ID}" + +aws configservice put-config-rule --config-rule "{ + \"ConfigRuleName\":\"$RULE_NAME\", + \"Source\":{\"Owner\":\"AWS\",\"SourceIdentifier\":\"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED\"}, + \"Scope\":{\"ComplianceResourceTypes\":[\"AWS::S3::Bucket\"]} +}" +echo "Rule created: $RULE_NAME" +``` + +Managed rules are predefined by AWS. `S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED` checks that each S3 bucket has a default encryption configuration. The `Scope` limits evaluation to S3 buckets only. + +## Step 4: Trigger evaluation + +Start a rule evaluation and wait for results. + +```bash +aws configservice start-config-rules-evaluation --config-rule-names "$RULE_NAME" +echo "Evaluation started — waiting 30 seconds for results..." +sleep 30 +``` + +Evaluations run asynchronously. The 30-second wait gives Config time to evaluate your buckets. For accounts with many buckets, evaluation may take longer. + +## Step 5: Check compliance details + +View per-resource compliance results. + +```bash +aws configservice get-compliance-details-by-config-rule --config-rule-name "$RULE_NAME" \ + --query 'EvaluationResults[:5].{Resource:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,Compliance:ComplianceType}' \ + --output table +``` + +Each result shows whether a specific bucket is `COMPLIANT` or `NON_COMPLIANT`. Buckets with default encryption enabled (SSE-S3 or SSE-KMS) are compliant. + +## Step 6: View compliance summary + +Get the overall compliance status for the rule. + +```bash +aws configservice describe-compliance-by-config-rule --config-rule-names "$RULE_NAME" \ + --query 'ComplianceByConfigRules[0].{Rule:ConfigRuleName,Compliance:Compliance.ComplianceType}' \ + --output table +``` + +The summary shows `COMPLIANT` only if all evaluated resources pass. If any bucket lacks encryption, the overall status is `NON_COMPLIANT`. + +## Cleanup + +Delete the Config rule. This does not affect your Config recorder or any S3 bucket configurations. + +```bash +aws configservice delete-config-rule --config-rule-name "$RULE_NAME" +``` + +Config charges $0.001 per rule evaluation in most Regions. Deleting the rule stops future evaluations and charges for this rule. + +The script automates all steps including cleanup: + +```bash +bash aws-config-gs.sh +``` + +## Related resources + +- [Getting started with AWS Config](https://docs.aws.amazon.com/config/latest/developerguide/getting-started.html) +- [AWS Config managed rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html) +- [Evaluating resources](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config.html) +- [AWS Config pricing](https://aws.amazon.com/config/pricing/) diff --git a/tuts/113-aws-config-gs/aws-config-gs.sh b/tuts/113-aws-config-gs/aws-config-gs.sh new file mode 100644 index 00000000..25235f0b --- /dev/null +++ b/tuts/113-aws-config-gs/aws-config-gs.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# Tutorial: Add a Config rule and check resource compliance +# Source: https://docs.aws.amazon.com/config/latest/developerguide/getting-started.html + +WORK_DIR=$(mktemp -d) +LOG_FILE="$WORK_DIR/config-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}} +if [ -z "$REGION" ]; then + echo "ERROR: No AWS region configured. Set one with: export AWS_DEFAULT_REGION=us-east-1" + exit 1 +fi +export AWS_DEFAULT_REGION="$REGION" +echo "Region: $REGION" + +RANDOM_ID=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) +RULE_NAME="tut-s3-encryption-${RANDOM_ID}" + +handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; } +trap 'handle_error $LINENO' ERR + +cleanup() { + echo "" + echo "Cleaning up resources..." + aws configservice delete-config-rule --config-rule-name "$RULE_NAME" 2>/dev/null && echo " Deleted rule $RULE_NAME" + rm -rf "$WORK_DIR" + echo "Cleanup complete." +} + +# Step 1: Check Config recorder status +echo "Step 1: Checking Config recorder" +RECORDER=$(aws configservice describe-configuration-recorder-status \ + --query 'ConfigurationRecordersStatus[0].{Name:name,Recording:recording}' --output table 2>/dev/null) +if [ -z "$RECORDER" ]; then + echo " No Config recorder found. Enable AWS Config in the console first." + echo " https://console.aws.amazon.com/config/home" + rm -rf "$WORK_DIR" + exit 1 +fi +echo "$RECORDER" + +# Step 2: List discovered resources +echo "Step 2: Listing discovered S3 buckets" +aws configservice list-discovered-resources --resource-type AWS::S3::Bucket \ + --query 'resourceIdentifiers[:5].{Type:resourceType,Id:resourceId}' --output table + +# Step 3: Add a managed rule +echo "Step 3: Adding managed rule: $RULE_NAME" +aws configservice put-config-rule --config-rule "{ + \"ConfigRuleName\":\"$RULE_NAME\", + \"Source\":{\"Owner\":\"AWS\",\"SourceIdentifier\":\"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED\"}, + \"Scope\":{\"ComplianceResourceTypes\":[\"AWS::S3::Bucket\"]} +}" +echo " Rule checks: S3 bucket server-side encryption" + +# Step 4: Trigger evaluation +echo "Step 4: Triggering rule evaluation" +aws configservice start-config-rules-evaluation --config-rule-names "$RULE_NAME" 2>/dev/null || true +echo " Evaluation started (takes 30-60 seconds)" +sleep 30 + +# Step 5: Check compliance +echo "Step 5: Compliance results" +aws configservice get-compliance-details-by-config-rule --config-rule-name "$RULE_NAME" \ + --query 'EvaluationResults[:5].{Resource:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,Compliance:ComplianceType}' --output table 2>/dev/null || \ + echo " No results yet — evaluation may still be running" + +# Step 6: Compliance summary +echo "Step 6: Compliance summary" +aws configservice describe-compliance-by-config-rule --config-rule-names "$RULE_NAME" \ + --query 'ComplianceByConfigRules[0].{Rule:ConfigRuleName,Compliance:Compliance.ComplianceType}' --output table + +echo "" +echo "Tutorial complete." +echo "Do you want to clean up all resources? (y/n): " +read -r CHOICE +if [[ "$CHOICE" =~ ^[Yy]$ ]]; then + cleanup +else + echo "Manual cleanup:" + echo " aws configservice delete-config-rule --config-rule-name $RULE_NAME" +fi