DEV Community

John  Ajera
John Ajera

Posted on

Block S3 Website with Terraform (Keep IP Access Ready)

Ever needed to block access to an S3-hosted static website while keeping it ready to quickly re-open with IP restrictions? Maybe you're doing maintenance, testing, or need to comply with security requirements.

This guide shows you how to enable S3 website hosting but block access for everyone using S3 Public Access Block, while keeping an IP allowlist policy ready for when you want to selectively re-open access.

Prerequisites

Before we dive in, make sure you have:

  • AWS CLI configured with appropriate permissions
  • Terraform installed (v1.0+)
  • Basic understanding of S3 bucket policies and Public Access Block settings

The Solution

Here's the complete Terraform configuration that achieves this:

# Define your allowed IP ranges
locals {
  allowed_cidr = [
    "203.0.113.10/32",    # Your office IP
    "198.51.100.0/24",    # Your VPN subnet
    "192.0.2.0/24"        # Additional trusted network
  ]
}

# Create the S3 bucket
resource "aws_s3_bucket" "website" {
  bucket = "your-static-website-bucket-name"
}

# Public Access Block: this is what *blocks* the website endpoint
resource "aws_s3_bucket_public_access_block" "website" {
  bucket = aws_s3_bucket.website.id

  block_public_acls       = true
  block_public_policy     = true  # <- This is the key setting that blocks access
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Enable website hosting (index/error pages)
resource "aws_s3_bucket_website_configuration" "website" {
  bucket = aws_s3_bucket.website.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

# Bucket policy with IP allowlist (kept ready for when you unblock)
resource "aws_s3_bucket_policy" "website" {
  bucket = aws_s3_bucket.website.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "AllowIPAccess"
        Effect    = "Allow"
        Principal = "*"
        Action    = "s3:GetObject"
        Resource  = "${aws_s3_bucket.website.arn}/*"
        Condition = {
          IpAddress = {
            "aws:SourceIp" = local.allowed_cidr
          }
        }
      }
    ]
  })

  depends_on = [aws_s3_bucket_public_access_block.website]
}
Enter fullscreen mode Exit fullscreen mode

How It Works

Let's break down each component:

1. Public Access Block Configuration

The aws_s3_bucket_public_access_block resource is the key to blocking access. When block_public_policy = true, it prevents any bucket policy from granting public access, effectively making your website return AccessDenied (403) to all visitors.

2. Website Configuration

The aws_s3_bucket_website_configuration enables the S3 website hosting feature, defining your index and error pages. This configuration remains active even when the site is blocked.

3. IP Allowlist Policy

The bucket policy defines which IP addresses can access your content. While block_public_policy = true prevents this policy from taking effect, it's ready to activate when you need it.

Current State

With this configuration deployed:

  • Website endpoint is blocked - Returns AccessDenied (403) to all visitors
  • IP allowlist policy is ready - Present but inactive due to public policy block
  • Website hosting remains enabled - Configuration stays in place

Re-opening with IP Restrictions

When you're ready to allow access from specific IPs:

  1. Update the Terraform configuration:
   resource "aws_s3_bucket_public_access_block" "website" {
     bucket = aws_s3_bucket.website.id

     block_public_acls       = true
     block_public_policy     = false  # <- Change this to false
     ignore_public_acls      = true
     restrict_public_buckets = true
   }
Enter fullscreen mode Exit fullscreen mode
  1. Apply the changes:
   terraform apply
Enter fullscreen mode Exit fullscreen mode
  1. Verify access:
    • Website loads for IPs in local.allowed_cidr
    • Returns AccessDenied for all other IPs

Troubleshooting

Common Issues

Issue: Website still accessible after setting block_public_policy = true

  • Solution: Ensure you're testing the website endpoint URL, not the S3 bucket URL directly

Issue: IP allowlist not working after unblocking

  • Solution: Verify your IP ranges in local.allowed_cidr are correct and include your current IP

Issue: Terraform apply fails with policy conflicts

  • Solution: Make sure depends_on = [aws_s3_bucket_public_access_block.website] is set on the bucket policy

Testing Your Setup

# Test from an allowed IP
curl -I https://your-bucket-name.s3-website-us-east-1.amazonaws.com/

# Test from a blocked IP (should return 403)
# Use a VPN or different network to test
Enter fullscreen mode Exit fullscreen mode

Security Considerations

  • Keep other Public Access Block settings enabled - Only change block_public_policy when needed
  • Regularly review your IP allowlist - Remove unused IPs and update ranges as needed
  • Monitor access logs - Enable S3 access logging to track who's accessing your site
  • Use least privilege - Only grant s3:GetObject permission, not broader S3 access

Next Steps

  • Consider implementing CloudFront for additional security layers
  • Set up monitoring alerts for unauthorized access attempts
  • Automate IP allowlist updates using AWS Systems Manager Parameter Store

Full example repository: tf-aws-s3-static-website-restricted-access

Have you used similar techniques for managing S3 website access? Share your experiences in the comments below! 🚀

Top comments (0)