AWS re/Start Lab · Challenge Lab

Challenge Lab: Amazon S3 from the CLI

A self-directed lab: no prescribed steps, just a goal. Create an S3 bucket from the AWS CLI, upload an HTML object, and make that single object publicly readable through the browser. The lab forces the operator to navigate Block Public Access defaults and resolve them with a least-privilege bucket policy.

Lab Summary

The lab runs entirely on the CLI Host EC2 instance. The official guide gives a list of goals (create bucket, upload object, make object public, list bucket) but no commands — this report documents the exact CLI path used to satisfy each goal, including the failed first attempt with a public ACL.

CLI-Only Workflow

Every action — bucket creation, object upload, ACL attempt, public access block removal, bucket policy application — was executed with aws s3 and aws s3api from the EC2 Instance Connect terminal.

Two Public-Access Paths Tried

First with put-object-acl --acl public-read (blocked by Block Public Access), then with a bucket policy scoped to a single object ARN, which is the path AWS officially supports today.

Step-by-Step Walkthrough

Bucket name used: superbucket3k184 · Region: us-west-2.

01

Configure the AWS CLI on the CLI Host

Connected to the CLI Host EC2 instance via EC2 Instance Connect and ran aws configure using the temporary lab credentials from the Vocareum credentials panel. Region us-west-2, output format json.

CLI Host shell — configure AWS CLI profile
aws configure
# AWS Access Key ID    [None]: AKIA…YDYYW3Z
# AWS Secret Access Key [None]: hwXHu…jQqNn
# Default region name   [None]: us-west-2
# Default output format [None]: json
02

Create the S3 bucket

Used aws s3api create-bucket with an explicit LocationConstraint because the bucket lives outside us-east-1. The API returns the bucket URL in the Location field, which is the confirmation that creation succeeded.

CLI Host shell — create the bucket in us-west-2
aws s3api create-bucket \
  --bucket superbucket3k184 \
  --region us-west-2 \
  --create-bucket-configuration LocationConstraint=us-west-2
stdout — JSON response
{
    "Location": "http://superbucket3k184.s3.amazonaws.com/"
}
Why LocationConstraint matters: us-east-1 is the legacy default region and the create-bucket API treats it as implicit — passing LocationConstraint=us-east-1 actually fails. Every other region requires it explicitly, including us-west-2 here.
03

Create and upload index.html

Wrote the HTML file inline with a heredoc, verified its content locally, then uploaded it to the bucket with aws s3 cp and listed the bucket to confirm the object was stored.

CLI Host shell — write the file with a heredoc
cat > index.html << 'EOF'
<html>
  <body>
    <h1>Amazon S3 Challenge Lab</h1>
    <p>This object was uploaded to an S3 bucket using AWS CLI.</p>
  </body>
</html>
EOF
CLI Host shell — upload and verify
aws s3 cp index.html s3://superbucket3k184/index.html
# upload: ./index.html to s3://superbucket3k184/index.html

aws s3 ls s3://superbucket3k184
# index.html

At this point the object exists in S3 but is not publicly readable. Opening the object URL in a browser returns AccessDenied.

04

First attempt: public ACL on the object — blocked

The intuitive first try is a public-read ACL on the object. It fails — and the failure is the most educational moment of the lab. S3 Block Public Access ships on by default since April 2023 for new buckets, and it intercepts public ACLs at the API level.

CLI Host shell — attempt a public-read ACL
aws s3api put-object-acl \
  --bucket superbucket3k184 \
  --key index.html \
  --acl public-read
stderr — AccessDenied
An error occurred (AccessDenied) when calling the PutObjectAcl operation:
User: arn:aws:iam::170644204079:user/awsstudent is not authorized to perform:
s3:PutObjectAcl on resource: "arn:aws:s3:::superbucket3k184/index.html"
because public ACLs are prevented by the BlockPublicAcls setting in
S3 Block Public Access.
Why this fails on purpose: The error explicitly names BlockPublicAcls. This is one of the four flags of Block Public Access: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets. The first two govern ACL-based public access; AWS now considers ACLs a legacy mechanism. The lesson the lab is teaching: do not rely on object ACLs for public access in 2024+.
05

Remove Block Public Access (only what's strictly required)

Before any bucket policy granting public read can be accepted, the BlockPublicPolicy flag has to come off. The lab uses delete-public-access-block, which clears the whole BPA config on the bucket.

CLI Host shell — drop the Block Public Access config
aws s3api delete-public-access-block \
  --bucket superbucket3k184
Order matters: If put-bucket-policy is attempted before dropping BPA, the policy is rejected with a similar AccessDenied because BlockPublicPolicy intercepts the API call. BPA → policy is the correct order.
06

Apply a least-privilege bucket policy

Wrote a JSON policy that grants s3:GetObject to anonymous principals, scoped to the single object index.html, not the entire bucket. This is the part of the lab that's a conscious security decision: the goal is "make the object public", not "make the bucket public".

CLI Host shell — create policy JSON with a heredoc
cat > object-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadOnlyIndexObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::superbucket3k184/index.html"
    }
  ]
}
EOF
CLI Host shell — apply the policy
aws s3api put-bucket-policy \
  --bucket superbucket3k184 \
  --policy file://object-policy.json
07

Verify public access in the browser

Opened the object URL in a regular browser tab — no AWS credentials, no signed URL. The HTML loaded and rendered the heading and paragraph that were defined in the heredoc, which is the lab's exit criterion.

Object URL
https://superbucket3k184.s3.us-west-2.amazonaws.com/index.html

Bucket Policy Breakdown

The policy is short but every field is deliberate. Worth annotating because this is the single artifact that grants public access.

Field Value Why it's that value
Version 2012-10-17 Latest IAM policy language version. Required — older versions don't support the newer condition operators.
Sid PublicReadOnlyIndexObject Optional statement identifier. Useful for auditing and for future aws s3api get-bucket-policy reads.
Effect Allow Grants instead of denies.
Principal "*" Anonymous / public. Anyone on the internet. This is the part that, combined with BlockPublicPolicy being off, actually makes the object public.
Action s3:GetObject Read-only. No PutObject, no DeleteObject, no listing. Public can fetch the file, nothing else.
Resource arn:aws:s3:::superbucket3k184/index.html Least-privilege choice. Not …/*. If a second object gets uploaded later, it stays private by default. The public surface area is exactly one file.
Wildcard vs. exact ARN: A Resource: "arn:aws:s3:::superbucket3k184/*" would be more convenient (any file dropped in the bucket would be public) but it's also strictly worse from a security standpoint. The lab's challenge brief said "make the object public" — taking that literally is the right call.

Command Reference

Quick reference of the AWS CLI calls used in this lab.

cmd

aws s3api create-bucket

Creates an S3 bucket. Outside us-east-1, you must pass --create-bucket-configuration LocationConstraint=<region>.

cmd

aws s3 cp · aws s3 ls

High-level S3 commands. cp uploads/downloads objects, ls lists bucket contents. Wrappers over s3api with friendlier UX.

cmd

aws s3api put-object-acl

Sets an ACL on a single object. Will fail on a bucket with default Block Public Access if the ACL is public — by design.

cmd

aws s3api delete-public-access-block

Removes the bucket's Block Public Access configuration entirely. Required before applying a public bucket policy. Surgical alternative: put-public-access-block with only the needed flags off.

cmd

aws s3api put-bucket-policy

Attaches a JSON policy to a bucket. The policy file is referenced as file://path — without that prefix the CLI tries to parse the path as inline JSON.

Key Learnings

What Was Actually Learned

Creating an S3 bucket from the CLI requires LocationConstraint for any region other than us-east-1.
S3 Block Public Access is on by default and will reject public ACL calls at the API layer — this is a deliberate guardrail, not a bug.
Public read access today is granted via bucket policy, not via object ACLs. ACLs are effectively legacy.
Scoping Resource to an exact object ARN keeps the rest of the bucket private. Wildcards are convenient but rarely justified.
The order is non-negotiable: drop Block Public Access → apply bucket policy. The other way around is rejected by the API.

Technical Conclusion

The lab is small in surface area — one bucket, one object, six commands — but it accurately maps to the production reality of exposing static assets on S3 in 2024+: ACLs are out, scoped bucket policies are in, and Block Public Access is the guardrail that forces you to opt into public exposure consciously.

The most valuable artifact is the AccessDenied error encountered with the public ACL attempt. It names BlockPublicAcls directly in the message, which is exactly the kind of explicit, debuggable error that S3's API surfaces — and it's the single signal that points you to the correct path (bucket policy) instead of trial and error.