Skip to content

Instantly share code, notes, and snippets.

@bewithdhanu
Last active June 30, 2026 10:34
Show Gist options
  • Select an option

  • Save bewithdhanu/d2cbb2a1cc4064f39a37b9744cc2cf6c to your computer and use it in GitHub Desktop.

Select an option

Save bewithdhanu/d2cbb2a1cc4064f39a37b9744cc2cf6c to your computer and use it in GitHub Desktop.
How to Secure AWS S3 with CloudFront, IAM Roles, and EC2 for Laravel (No Access Keys)

AWS S3 Secure Setup with CloudFront + EC2 IAM Role (Laravel Production Guide)

Production-ready AWS architecture guide for securing S3 with CloudFront and EC2 IAM Roles in Laravel applications.

This setup removes AWS access keys completely and uses IAM Role-based authentication for EC2 instances. It includes private S3 bucket configuration, CloudFront restriction, and Laravel Storage integration.


Architecture Summary

  • Private S3 bucket (no public access)
  • CloudFront as the only public entry point
  • EC2 IAM Role (no static AWS credentials)
  • Laravel AWS SDK integration using IAM Role

1. Secure S3 Bucket (Disable Public Access)

Step 1: Enable Block Public Access

Go to: S3 → Bucket → Permissions → Block Public Access

Enable ALL:

  • ❌ Block all public access = ON

Step 2: S3 Bucket Policy (CloudFront Only Access)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOnly",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
        }
      }
    }
  ]
}

Ensure you REMOVE:

  • Public s3:GetObject
  • Public s3:ListBucket
  • Any IP-based restrictions

2. IAM Policy for EC2 Role (Least Privilege)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
    }
  ]
}

Name:

  • ec2-your-project-s3-role-policy

3. Create IAM Role for EC2

Step 1

  • Trusted entity: AWS service
  • Use case: EC2

Step 2

Attach policy:

  • ec2-your-project-s3-role-policy

Step 3

Create role:

  • ec2-your-project-s3-role

4. Attach IAM Role to EC2

EC2 → Instance → Actions → Security → Modify IAM Role Attach:

  • ec2-your-project-s3-role

5. Verify IAM Role

aws sts get-caller-identity

Expected:

{
  "Arn": "arn:aws:sts::ACCOUNT_ID:assumed-role/ec2-your-project-s3-role/i-xxxx"
}

6. Test S3 Access from EC2

aws s3 ls s3://YOUR_BUCKET_NAME

Expected:

  • Bucket contents listed

7. Remove AWS Keys from Laravel

Remove from .env:

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN

Keep only:

AWS_DEFAULT_REGION=us-east-2
AWS_BUCKET=YOUR_BUCKET_NAME
AWS_USE_PATH_STYLE_ENDPOINT=false

8. Clear Laravel Cache

sudo -u www-data php artisan config:clear
sudo -u www-data php artisan cache:clear
sudo -u www-data php artisan config:cache

9. Restart Services

sudo systemctl restart apache2
sudo systemctl restart php8.*-fpm

10. Laravel Test Route

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;

Route::get('/aws-test', function () {
    $result = Storage::disk('s3')->put('test.txt', 'hello from ec2 role');

    return [
        'uploaded' => $result,
        'url' => Storage::disk('s3')->url('test.txt')
    ];
});

11. Test Execution

Run:

curl http://localhost/aws-test

Expected Response:

{
  "uploaded": true,
  "url": "https://bucket.s3.region.amazonaws.com/test.txt"
}

Verify in S3:

aws s3 ls s3://YOUR_BUCKET_NAME

Expected:

2026-06-30 09:54:13        19 test.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment