One of the most common security mistakes in AWS is exposing Amazon S3 buckets to the public. While public access may be useful for testing, production workloads should always access S3 privately through AWS networking and IAM.
In this hands-on lab, we'll start with a publicly accessible S3 bucket, verify that anyone can access its contents, and then progressively secure it using:
- Amazon S3
- VPC Gateway Endpoint
- IAM Role for EC2
- Bucket Policy with VPC Endpoint restriction
By the end of this tutorial, your S3 bucket will only be accessible from resources inside your VPC.
Architecture
Internet
|
(Access Denied)
X
|
-----------------------
| Amazon S3 |
-----------------------
^
|
Gateway VPC Endpoint
|
----------------
| VPC |
| |
| EC2 + IAM |
----------------
Step 1: Create an S3 Bucket
Navigate to Amazon S3 from the AWS Console.
Click Create bucket.
Use the bucket name:
ml-lab-bucket-secure-ml-101
For this lab:
- Disable Block all public access
- Accept the warning
- Create the bucket
At this point the bucket is capable of serving public objects.
Step 2: Allow Public Read Access
Open the bucket and navigate to:
Permissions → Bucket Policy
Paste the following policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::ml-lab-bucket-secure-ml-101/*"
}
]
}
Save the policy.
This policy allows anyone on the internet to download objects from the bucket.
Step 3: Upload a Test File
Open the Objects tab.
Upload:
raw_v1.csv
After uploading:
- Open the object.
- Copy its Object URL.
- Paste it into a browser.
The CSV downloads successfully without authentication.
✅ This confirms the bucket is publicly accessible.
Step 4: Create a VPC Gateway Endpoint for Amazon S3
Search for VPC in the AWS Console.
Navigate to:
Endpoints
Click Create endpoint.
Configuration:
Name
my-ps-lab-endpoint-01
Service:
com.amazonaws.us-east-1.s3
Type:
Gateway
Choose:
- Your VPC
- Your Route Table
Create the endpoint.
Step 5: Verify the Route Table
Navigate to:
VPC → Route Tables
Open the selected route table.
Under Routes, verify a route similar to:
vpce-xxxxxxxx
Copy the endpoint ID.
You'll use it later in the bucket policy.
Step 6: Launch an EC2 Instance
Open EC2.
Launch a new instance.
Example name:
my-ps-lab-instance-01
Choose:
- Your VPC
- Your subnet
- Default key pair
Launch the instance.
Step 7: Create an IAM Role
Navigate to IAM → Roles.
Create a new role.
Configuration:
Trusted Entity
AWS Service
Use Case
EC2
Permissions:
AmazonS3FullAccess
Role Name
my-iam-role-lab-01
Create the role.
Step 8: Add an Inline IAM Policy
Open the IAM role.
Choose:
Add permissions
→ Create Inline Policy
→ JSON
Paste:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::ml-lab-bucket-secure-ml-101"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::ml-lab-bucket-secure-ml-101/*"
}
]
}
Policy name:
my-secure-policy
Create the policy.
Although the role already has AmazonS3FullAccess, this inline policy demonstrates how to grant least-privilege permissions for a specific bucket.
Step 9: Attach the IAM Role to EC2
Go to the EC2 console.
Select the instance.
Choose:
Actions
→ Security
→ Modify IAM Role
Attach:
my-iam-role-lab-01
Update the role.
Step 10: Test Access from EC2
Connect to the EC2 instance using EC2 Instance Connect.
Run:
aws s3 ls s3://ml-lab-bucket-secure-ml-101
Expected output:
raw_v1.csv
You can also test another bucket:
aws s3 ls s3://your-s3-bucket-name
At this stage, EC2 can access the bucket successfully.
Step 11: Block Public Access
Return to the S3 bucket.
Navigate to:
Permissions
Edit Block Public Access.
Enable:
Block all public access
Save the settings.
Type:
confirm
to apply the changes.
The bucket is no longer publicly accessible.
Step 12: Restrict Access to the VPC Endpoint
Now we'll ensure that only requests coming through our VPC Endpoint are allowed.
Replace the bucket policy with:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyIfNotFromVPCE",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::ml-lab-bucket-secure-ml-101",
"arn:aws:s3:::ml-lab-bucket-secure-ml-101/*"
],
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "vpce-xxxxxxxx"
}
}
}
]
}
Replace:
vpce-xxxxxxxx
with your own VPC Endpoint ID.
Save the policy.
Step 13: Validate the Security
Test from Browser
Open the Object URL again.
Expected result:
Access Denied
The internet can no longer reach your bucket.
Test from EC2
Return to the EC2 terminal.
Run:
aws s3 ls s3://ml-lab-bucket-secure-ml-101
Expected output:
raw_v1.csv
The EC2 instance continues to access the bucket successfully because traffic flows through the VPC Gateway Endpoint.
What We Achieved
In this lab, we transformed an insecure S3 bucket into a private, production-ready storage solution.
✔ Created an Amazon S3 bucket
✔ Allowed public object access
✔ Verified public accessibility
✔ Created a Gateway VPC Endpoint
✔ Launched an EC2 instance
✔ Assigned an IAM Role
✔ Granted least-privilege bucket permissions
✔ Enabled Block Public Access
✔ Restricted S3 access using a VPC Endpoint policy
✔ Verified that only workloads inside the VPC could access the bucket













Top comments (0)