Get started with a basic CloudFront distribution
The following code example shows how to:
Create an Amazon S3 bucket
Upload content to the bucket
Create a CloudFront distribution with OAC
Clean up resources
- Bash
-
- AWS CLI with Bash script
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials
repository. #!/bin/bash # CloudFront Getting Started Tutorial Script # This script creates an S3 bucket, uploads sample content, creates a CloudFront distribution with OAC, # and demonstrates how to access content through CloudFront. set -euo pipefail # Set up logging LOG_FILE="cloudfront-tutorial.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "Starting CloudFront Getting Started Tutorial at $(date)" # Function to handle errors handle_error() { echo "ERROR: $1" >&2 echo "Resources created before error:" if [ -n "${BUCKET_NAME:-}" ]; then echo "- S3 Bucket: $BUCKET_NAME" fi if [ -n "${OAC_ID:-}" ]; then echo "- CloudFront Origin Access Control: $OAC_ID" fi if [ -n "${DISTRIBUTION_ID:-}" ]; then echo "- CloudFront Distribution: $DISTRIBUTION_ID" fi echo "Attempting to clean up resources..." cleanup exit 1 } # Function to clean up resources cleanup() { echo "Cleaning up resources..." if [ -n "${DISTRIBUTION_ID:-}" ]; then echo "Disabling CloudFront distribution $DISTRIBUTION_ID..." # Get the current configuration and ETag in one call DIST_CONFIG=$(aws cloudfront get-distribution-config --id "$DISTRIBUTION_ID" 2>/dev/null) || { echo "Failed to get distribution config. Continuing with cleanup..." DIST_CONFIG="" } if [ -n "$DIST_CONFIG" ]; then ETAG=$(echo "$DIST_CONFIG" | jq -r '.ETag') # Modify and update distribution in one pipeline if echo "$DIST_CONFIG" | jq '.DistributionConfig.Enabled = false' | \ aws cloudfront update-distribution \ --id "$DISTRIBUTION_ID" \ --distribution-config "$(cat)" \ --if-match "$ETAG" 2>/dev/null; then echo "Waiting for distribution to be disabled (this may take several minutes)..." aws cloudfront wait distribution-deployed --id "$DISTRIBUTION_ID" 2>/dev/null || { echo "Distribution deployment wait timed out. Proceeding with deletion..." } # Get fresh ETag for deletion DIST_CONFIG=$(aws cloudfront get-distribution-config --id "$DISTRIBUTION_ID" 2>/dev/null) || { echo "Failed to get updated config. Skipping distribution deletion..." DIST_CONFIG="" } if [ -n "$DIST_CONFIG" ]; then ETAG=$(echo "$DIST_CONFIG" | jq -r '.ETag') aws cloudfront delete-distribution --id "$DISTRIBUTION_ID" --if-match "$ETAG" 2>/dev/null && \ echo "CloudFront distribution deleted." || \ echo "Failed to delete distribution. You may need to delete it manually." fi else echo "Failed to disable distribution. Continuing with cleanup..." fi fi fi if [ -n "${OAC_ID:-}" ]; then echo "Deleting Origin Access Control $OAC_ID..." OAC_DATA=$(aws cloudfront get-origin-access-control --id "$OAC_ID" 2>/dev/null) || { echo "Failed to get Origin Access Control. You may need to delete it manually." OAC_DATA="" } if [ -n "$OAC_DATA" ]; then OAC_ETAG=$(echo "$OAC_DATA" | jq -r '.ETag') aws cloudfront delete-origin-access-control --id "$OAC_ID" --if-match "$OAC_ETAG" 2>/dev/null && \ echo "Origin Access Control deleted." || \ echo "Failed to delete Origin Access Control. You may need to delete it manually." fi fi if [ -n "${BUCKET_NAME:-}" ] && [ "$BUCKET_IS_SHARED" != "true" ]; then echo "Deleting S3 bucket $BUCKET_NAME and its contents..." aws s3 rm "s3://$BUCKET_NAME" --recursive 2>/dev/null || { echo "Failed to remove bucket contents. Continuing with bucket deletion..." } aws s3 rb "s3://$BUCKET_NAME" 2>/dev/null && \ echo "S3 bucket deleted." || \ echo "Failed to delete bucket. You may need to delete it manually." fi # Clean up temporary files rm -f temp_disabled_config.json distribution-config.json bucket-policy.json 2>/dev/null || true rm -rf temp_content 2>/dev/null || true } # Trap errors and cleanup trap 'handle_error "Script interrupted"' INT TERM # Validate AWS CLI is available if ! command -v aws &> /dev/null; then handle_error "AWS CLI is not installed or not in PATH" fi # Validate jq is available if ! command -v jq &> /dev/null; then handle_error "jq is not installed or not in PATH" fi # Validate AWS credentials are configured if ! aws sts get-caller-identity &> /dev/null; then handle_error "AWS credentials are not configured or invalid" fi # Initialize variables BUCKET_NAME="" OAC_ID="" DISTRIBUTION_ID="" BUCKET_IS_SHARED=false # Generate a random identifier for the bucket name using secure random RANDOM_ID=$(openssl rand -hex 6) if [ -z "$RANDOM_ID" ]; then handle_error "Failed to generate random identifier" fi # Check for shared prereq bucket and get account ID in parallel calls PREREQ_BUCKET=$(aws cloudformation describe-stacks --stack-name tutorial-prereqs-bucket \ --query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text 2>/dev/null) || PREREQ_BUCKET="" if [ -n "$PREREQ_BUCKET" ] && [ "$PREREQ_BUCKET" != "None" ]; then BUCKET_NAME="$PREREQ_BUCKET" BUCKET_IS_SHARED=true echo "Using shared bucket: $BUCKET_NAME" else BUCKET_IS_SHARED=false BUCKET_NAME="cloudfront-${RANDOM_ID}" fi echo "Using bucket name: $BUCKET_NAME" # Get AWS account ID early ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) if [ $? -ne 0 ]; then handle_error "Failed to get AWS account ID" fi # Validate account ID format if ! [[ "$ACCOUNT_ID" =~ ^[0-9]{12}$ ]]; then handle_error "Invalid AWS account ID format: $ACCOUNT_ID" fi # Create a temporary directory for content with restrictive permissions TEMP_DIR="temp_content" mkdir -p "$TEMP_DIR/css" chmod 700 "$TEMP_DIR" if [ $? -ne 0 ]; then handle_error "Failed to create temporary directory" fi # Step 1: Create an S3 bucket (only if not shared) if [ "$BUCKET_IS_SHARED" != "true" ]; then echo "Creating S3 bucket: $BUCKET_NAME" aws s3 mb "s3://$BUCKET_NAME" --region us-east-1 if [ $? -ne 0 ]; then handle_error "Failed to create S3 bucket" fi aws s3api put-bucket-tagging --bucket "$BUCKET_NAME" --tagging 'TagSet=[{Key=project,Value=doc-smith},{Key=tutorial,Value=cloudfront-gettingstarted}]' # Batch bucket configuration calls for efficiency aws s3api put-bucket-versioning --bucket "$BUCKET_NAME" --versioning-configuration Status=Enabled & aws s3api put-public-access-block \ --bucket "$BUCKET_NAME" \ --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" & aws s3api put-bucket-encryption \ --bucket "$BUCKET_NAME" \ --server-side-encryption-configuration '{ "Rules": [ { "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "AES256" } } ] }' & wait fi # Step 2: Create sample content echo "Creating sample content..." cat > "$TEMP_DIR/index.html" << 'EOF' <!DOCTYPE html> <html> <head> <title>Hello World</title> <link rel="stylesheet" type="text/css" href="css/styles.css"> </head> <body> <h1>Hello world!</h1> </body> </html> EOF cat > "$TEMP_DIR/css/styles.css" << 'EOF' body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; } h1 { color: #333; text-align: center; } EOF # Set restrictive permissions on content files chmod 600 "$TEMP_DIR/index.html" "$TEMP_DIR/css/styles.css" # Step 3: Upload content to the S3 bucket with encryption and metadata echo "Uploading content to S3 bucket..." aws s3 cp "$TEMP_DIR/" "s3://$BUCKET_NAME/" --recursive \ --sse AES256 \ --metadata "Source=CloudFrontTutorial" if [ $? -ne 0 ]; then handle_error "Failed to upload content to S3 bucket" fi # Step 4: Create Origin Access Control echo "Creating Origin Access Control..." OAC_RESPONSE=$(aws cloudfront create-origin-access-control \ --origin-access-control-config Name="oac-for-$BUCKET_NAME",SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3) if [ $? -ne 0 ]; then handle_error "Failed to create Origin Access Control" fi OAC_ID=$(echo "$OAC_RESPONSE" | jq -r '.OriginAccessControl.Id') if [ -z "$OAC_ID" ] || [ "$OAC_ID" = "null" ]; then handle_error "Failed to extract OAC ID from response" fi # Validate OAC ID format (alphanumeric and hyphens) if ! [[ "$OAC_ID" =~ ^[A-Z0-9]+$ ]]; then handle_error "Invalid OAC ID format: $OAC_ID" fi echo "Created Origin Access Control with ID: $OAC_ID" # Step 5: Create CloudFront distribution echo "Creating CloudFront distribution..." # Validate bucket name format if ! [[ "$BUCKET_NAME" =~ ^[a-z0-9][a-z0-9.-]*[a-z0-9]$ ]]; then handle_error "Invalid S3 bucket name format: $BUCKET_NAME" fi # Create distribution configuration with improved security settings cat > distribution-config.json << EOF { "CallerReference": "cli-tutorial-$(date +%s)", "Origins": { "Quantity": 1, "Items": [ { "Id": "S3-$BUCKET_NAME", "DomainName": "$BUCKET_NAME.s3.amazonaws.com", "S3OriginConfig": { "OriginAccessIdentity": "" }, "OriginAccessControlId": "$OAC_ID" } ] }, "DefaultCacheBehavior": { "TargetOriginId": "S3-$BUCKET_NAME", "ViewerProtocolPolicy": "redirect-to-https", "AllowedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"], "CachedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] } }, "DefaultTTL": 86400, "MinTTL": 0, "MaxTTL": 31536000, "Compress": true, "ForwardedValues": { "QueryString": false, "Cookies": { "Forward": "none" } } }, "Comment": "CloudFront distribution for tutorial", "Enabled": true, "WebACLId": "", "HttpVersion": "http2and3" } EOF # Set restrictive permissions on config file before passing credentials chmod 600 distribution-config.json DIST_RESPONSE=$(aws cloudfront create-distribution --distribution-config file://distribution-config.json) if [ $? -ne 0 ]; then handle_error "Failed to create CloudFront distribution" fi DISTRIBUTION_ID=$(echo "$DIST_RESPONSE" | jq -r '.Distribution.Id') DOMAIN_NAME=$(echo "$DIST_RESPONSE" | jq -r '.Distribution.DomainName') if [ -z "$DISTRIBUTION_ID" ] || [ "$DISTRIBUTION_ID" = "null" ] || [ -z "$DOMAIN_NAME" ] || [ "$DOMAIN_NAME" = "null" ]; then handle_error "Failed to extract distribution ID or domain name from response" fi # Validate distribution ID format if ! [[ "$DISTRIBUTION_ID" =~ ^[A-Z0-9]+$ ]]; then handle_error "Invalid distribution ID format: $DISTRIBUTION_ID" fi echo "Created CloudFront distribution with ID: $DISTRIBUTION_ID" echo "CloudFront domain name: $DOMAIN_NAME" # Tag the CloudFront distribution aws cloudfront tag-resource --resource "arn:aws:cloudfront::$ACCOUNT_ID:distribution/$DISTRIBUTION_ID" --tags 'Items=[{Key=project,Value=doc-smith},{Key=tutorial,Value=cloudfront-gettingstarted}]' # Step 6: Update S3 bucket policy echo "Updating S3 bucket policy..." cat > bucket-policy.json << EOF { "Version":"2012-10-17", "Statement": [ { "Sid": "AllowCloudFrontServicePrincipal", "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::$BUCKET_NAME/*", "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::$ACCOUNT_ID:distribution/$DISTRIBUTION_ID" } } } ] } EOF # Set restrictive permissions on policy file chmod 600 bucket-policy.json aws s3api put-bucket-policy --bucket "$BUCKET_NAME" --policy file://bucket-policy.json if [ $? -ne 0 ]; then handle_error "Failed to update S3 bucket policy" fi # Step 7: Wait for distribution to deploy echo "Waiting for CloudFront distribution to deploy (this may take 5-10 minutes)..." aws cloudfront wait distribution-deployed --id "$DISTRIBUTION_ID" 2>/dev/null || { echo "Warning: Distribution deployment wait timed out. The distribution may still be deploying." } echo "CloudFront distribution is now deployed." # Step 8: Display access information echo "" echo "===== CloudFront Distribution Setup Complete =====" echo "You can access your content at: https://$DOMAIN_NAME/index.html" echo "" echo "Resources created:" echo "- S3 Bucket: $BUCKET_NAME" echo "- CloudFront Origin Access Control: $OAC_ID" echo "- CloudFront Distribution: $DISTRIBUTION_ID" echo "" echo "To clean up resources, run: cleanup" echo "" echo "Tutorial completed at $(date)"-
For API details, see the following topics in AWS CLI Command Reference.
-
For a complete list of AWS SDK developer guides and code examples, see Using CloudFront with an AWS SDK. This topic also includes information about getting started and details about previous SDK versions.
Delete signing resources
Getting started with WAF