Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session goes deep into S3 — the most widely used AWS service and a guaranteed topic in every AWS certification and DevOps interview.
📋 Topics Covered
| # | Topic | Type |
|---|---|---|
| 1 | Why S3 Matters + Quick Recap | Concept |
| 2 | JSON & YAML — Why They Matter in AWS | Concept + Interview |
| 3 | API Calls in AWS (GET, PUT, DELETE) | Concept + DevOps |
| 4 | Region Selection — Why It Matters for S3 | Concept + Cert |
| 5 | S3 Buckets — Rules & Constraints | Concept + Lab |
| 6 | S3 Objects — What They Actually Are | Concept + Cert |
| 7 | S3 Object URL Structure | Concept + Lab |
| 8 | S3 Storage Classes | Concept + Cert |
| 9 | S3 Versioning | Concept + Lab |
| 10 | S3 Encryption — SSE-S3, SSE-KMS, SSE-C, CSE | Concept + Cert |
| 11 | S3 Bucket Keys — KMS Cost Optimization | Concept + Cert |
| 12 | Bucket Policies — Ownership & ACL Settings | Concept + Interview |
| 13 | S3 Access Control — Policies, ACLs, Public Access | Concept + Cert |
| 14 | Explicit Deny — The Override Rule | Concept + Interview |
| 15 | AWS CloudTrail + S3 Audit Logging | Concept + DevOps |
| 16 | VPC — Brief Introduction | Concept |
| 17 | S3 CLI Commands | Practical |
| 18 | boto3 — S3 with Python | Practical |
| 19 | Interview Questions | Interview |
| 20 | Practice Tasks | Practice |
Why S3 Matters
S3 (Simple Storage Service) is arguably the most important AWS service to understand — it appears in almost every architecture, every certification, and every DevOps workflow. Backups, static websites, data lakes, CI/CD artifacts, ML training data, log archives — S3 underpins all of it.
Before diving into encryption and policies, two foundational things from this session need to be covered first: JSON/YAML code and API calls — because everything in S3 (and AWS) is built on top of these.
JSON & YAML — Why Interviewers Ask You to Write Them
In real organizations, nobody clicks through the AWS Console to create resources. Everything is written as code — JSON or YAML — and executed via CLI, CloudFormation, or Terraform. Interviewers increasingly ask candidates to write infrastructure code on the spot (sometimes called "vibe coding").
JSON (JavaScript Object Notation):
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"MyEC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-0abcdef1234567890",
"InstanceType": "t2.micro",
"KeyName": "my-key-pair",
"Tags": [
{ "Key": "Name", "Value": "MyServer" }
]
}
}
}
}
YAML (Yet Another Markup Language) — same thing, cleaner to read:
AWSTemplateFormatVersion: "2010-09-09"
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0abcdef1234567890
InstanceType: t2.micro
KeyName: my-key-pair
Tags:
- Key: Name
Value: MyServer
Key differences:
| JSON | YAML | |
|---|---|---|
| Syntax | Curly braces {}, quotes "", commas |
Indentation, colons :, cleaner |
| Readability | Verbose, harder to read | Clean, easier to write by hand |
| Comments | ❌ Not supported | ✅ Supported with #
|
| Used in AWS for | API responses, Lambda, IAM policies | CloudFormation, SAM, CodePipeline |
| Error-prone | Missing comma or brace | Wrong indentation |
S3 Bucket Policy in JSON (what you'll write in real interviews):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket-name/*"
}
]
}
IAM Policy in YAML (CloudFormation):
Type: AWS::IAM::Policy
Properties:
PolicyName: S3ReadPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:ListBucket
Resource:
- arn:aws:s3:::my-bucket
- arn:aws:s3:::my-bucket/*
🎯 Interview tip: Practice writing a basic S3 bucket policy and EC2 IAM role in both JSON and YAML without looking things up. Interviewers genuinely ask for this now.
API Calls in AWS — How Everything Actually Works
Every click in the AWS Console, every CLI command, every boto3 call — all of it is ultimately an HTTP API call under the hood. Understanding this is essential for DevOps and automation work.
AWS uses standard HTTP methods:
| Method | Purpose | S3 Example |
|---|---|---|
GET |
Read / retrieve data | Download an object, list buckets |
PUT |
Create or replace | Upload an object, create a bucket |
POST |
Create or trigger | Initiate multipart upload |
DELETE |
Remove | Delete an object or bucket |
HEAD |
Get metadata only | Check if object exists without downloading |
EC2 → S3 interaction via API:
EC2 Instance
│
│ PUT /my-bucket/backup.zip (upload)
│ GET /my-bucket/config.json (download)
│ DELETE /my-bucket/old.log (delete)
▼
S3 API Endpoint: https://s3.ap-south-1.amazonaws.com
│
▼
S3 Bucket (my-bucket)
In practice, your application on EC2 uses the AWS SDK (boto3 in Python, or the Java/Node SDK) which converts your code calls into these signed HTTP API requests automatically.
AWS Signature Version 4 — all API calls must be signed with your credentials so AWS can verify identity. The SDK handles this for you when using IAM roles (recommended) or access keys (not recommended).
🎯 DevOps relevance: When you write a Python script with boto3 to copy files to S3, you're making PUT API calls. When your Lambda function reads from DynamoDB, it's making GET API calls. The console is just a visual wrapper over the same APIs.
Region Selection — Why It Matters for S3
S3 is a global service in name, but buckets are created in a specific Region. Choosing the right region affects:
| Factor | Impact on S3 |
|---|---|
| Performance | Store data in Region closest to your users/EC2 → lower latency |
| Cost | S3 pricing varies by Region (US East is typically cheapest) |
| Data Transfer | Transferring data between Regions costs money — keep S3 and EC2 in same Region |
| Compliance / Regulatory | GDPR (EU), RBI (India) may legally require data in specific regions |
| Disaster Recovery | Cross-Region Replication (CRR) copies to another Region for DR |
💡 Practical rule: Always create your S3 bucket in the same Region as the EC2 instances or services that will use it. Cross-region data transfer is charged per GB and adds latency.
S3 Buckets — Rules & Constraints
A bucket is the container for your objects in S3. Think of it like a top-level folder — except with globally unique naming rules and no nesting of buckets inside buckets.
Bucket Naming Rules
✅ Must be globally unique — no two buckets in the world can share a name
✅ 3 to 63 characters long
✅ Lowercase letters, numbers, and hyphens only
✅ Must start with a letter or number
✅ Must not start with "xn--" (reserved)
✅ Must not end with "-s3alias"
❌ No uppercase letters
❌ No underscores
❌ No IP address format (e.g., 192.168.1.1)
❌ No spaces
Good names: tejas-dev-backups, company-prod-logs-2026, my-static-site-assets
Bad names: My_Bucket, 123.456.789.0, TejasBucket
Key Bucket Facts
- One Region per bucket — created in a specific Region, stays there
- No size limit on total bucket storage — scales infinitely
- 5 TB max per individual object — single file can't exceed 5TB
- Soft limit of 100 buckets per account (can request increase to 1000)
- No nested buckets — you cannot put a bucket inside a bucket
- Bucket names in URLs — because names are globally unique, they're used directly in URLs
S3 Objects — What They Actually Are
An object is any file stored in S3. Unlike a traditional file system, S3 is flat — there are no real folders (just prefixes that look like folders).
Object Components
| Component | Description |
|---|---|
| Key | The full "path" of the object. e.g., reports/2026/january/sales.csv
|
| Value | The actual file content (bytes) |
| Version ID | Unique ID if versioning is enabled |
| Metadata | Key-value pairs about the object (content-type, custom tags) |
| ETag | MD5 hash of the object (used to verify integrity) |
| Storage Class | Standard, IA, Glacier, etc. |
The "Folder" Illusion
Bucket: my-data
Objects stored as:
reports/2026/sales.csv ← key includes the "path"
reports/2026/revenue.csv
images/logo.png
images/banner.jpg
config.json
There are NO real folders. "reports/2026/" is just a prefix.
The console shows them as folders for readability — but it's flat storage underneath.
Object Size Limits
| Upload type | Size limit | When to use |
|---|---|---|
| Single PUT upload | Up to 5 GB | Small to medium files |
| Multipart upload | Up to 5 TB | Files larger than 100 MB (recommended) |
| Minimum part size | 5 MB | For multipart upload chunks |
✅ Best practice: Use multipart upload for any file over 100 MB. It's faster (parallel chunks), more reliable (resume on failure), and required above 5 GB.
S3 Object URL Structure
Every object in S3 has a predictable, deterministic URL. Understanding the format is important for linking to files, debugging access issues, and building applications.
URL Formats
Path-style URL (older, being deprecated):
https://s3.<region>.amazonaws.com/<bucket-name>/<object-key>
https://s3.ap-south-1.amazonaws.com/tejas-dev/reports/sales.csv
Virtual-hosted-style URL (current standard):
https://<bucket-name>.s3.<region>.amazonaws.com/<object-key>
https://tejas-dev.s3.ap-south-1.amazonaws.com/reports/sales.csv
Breaking it down:
https://tejas-dev.s3.ap-south-1.amazonaws.com/reports/2026/sales.csv
│ │ │ │
bucket │ region object key (full path)
S3 service
URL Validity
An S3 object URL is only valid while:
- The object actually exists in the bucket
- The object is publicly accessible (or you're using a pre-signed URL with a valid expiry)
If you delete the object, the URL returns a 404 — the URL structure stays the same but there's nothing at that address.
Pre-Signed URLs
For private objects, you can generate a pre-signed URL — a temporary link with an expiry time that grants access without making the object public.
import boto3
s3 = boto3.client('s3')
# Generate a URL valid for 1 hour
url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': 'my-private-bucket', 'Key': 'secret-file.pdf'},
ExpiresIn=3600 # seconds
)
print(url)
# https://my-private-bucket.s3.ap-south-1.amazonaws.com/secret-file.pdf?X-Amz-Signature=...&X-Amz-Expires=3600
🎯 Common use case: Share a download link for a private file with a customer for 24 hours — pre-signed URL with
ExpiresIn=86400. Link expires automatically.
S3 Storage Classes
S3 isn't one-size-fits-all for storage. Different classes trade cost vs access speed vs retrieval time.
| Storage Class | Use Case | Retrieval | Cost |
|---|---|---|---|
| S3 Standard | Frequently accessed data | Milliseconds | Highest |
| S3 Intelligent-Tiering | Unknown or changing access patterns | Milliseconds | Moderate (auto-tiered) |
| S3 Standard-IA | Infrequent access, needs fast retrieval | Milliseconds | Lower storage, retrieval fee |
| S3 One Zone-IA | Infrequent, reproducible data, one AZ only | Milliseconds | Lower than Standard-IA |
| S3 Glacier Instant | Archive, accessed once a quarter | Milliseconds | Very low |
| S3 Glacier Flexible | Archive, minutes-to-hours retrieval ok | Minutes to hours | Very low |
| S3 Glacier Deep Archive | Long-term archive, accessed once a year | Up to 12 hours | Cheapest |
Simple decision guide:
Serving to users daily? → Standard
Don't know access pattern? → Intelligent-Tiering (auto-manages)
Accessed once a month, need fast? → Standard-IA
Old logs, rarely accessed? → Glacier Instant Retrieval
Legal archive, won't need for years? → Glacier Deep Archive
Lifecycle Policies (Auto-Tiering)
Just like EFS, you can set rules to automatically move objects between storage classes:
Day 0 → Upload to Standard
Day 30 → Auto-move to Standard-IA
Day 90 → Auto-move to Glacier Instant
Day 365 → Auto-move to Glacier Deep Archive
Day 2555 → Auto-delete (7 years, for compliance)
🎯 Cert tip: "How do you minimize S3 storage costs for logs that need to be kept for 7 years but rarely accessed after 30 days?" → Lifecycle policy transitioning to Glacier.
S3 Versioning
When versioning is enabled, S3 keeps every version of every object — overwrites create a new version instead of replacing, and deletes add a "delete marker" instead of actually deleting.
Upload file.txt (v1) → Version ID: aaa111
Upload file.txt (v2) → Version ID: bbb222 ← current version
Upload file.txt (v3) → Version ID: ccc333 ← current version
GET file.txt → returns v3 (current)
GET file.txt?versionId=aaa111 → returns v1
DELETE file.txt → adds delete marker, v3 still exists
Why versioning matters:
- Accidental overwrite? Restore previous version instantly
- Accidental delete? Remove the delete marker to recover
- Required for Cross-Region Replication
- Required for S3 Object Lock (compliance)
⚠️ Cost warning: Versioning stores every version of every file. An object updated 100 times = 100 stored copies. Enable Lifecycle Policies to expire old versions, otherwise storage costs grow silently.
S3 Encryption — Four Methods
Encryption in S3 has two dimensions: at rest (stored on disk) and in transit (moving over the network). Transit is always HTTPS — the four methods below are all about at rest.
SSE-S3 (Server-Side Encryption with S3-Managed Keys)
- AWS manages everything — keys are created, rotated, and used by S3 automatically
- Enabled by default on all new buckets (since Jan 2023)
- Zero configuration needed
- AES-256 encryption
- You have no control over the keys
Upload file → S3 auto-encrypts with its own key → stored encrypted
Download → S3 auto-decrypts → you get the file
You never see or touch the key
Use when: You need encryption but don't have compliance requirements about key control.
SSE-KMS (Server-Side Encryption with AWS KMS Keys)
Used mostly
Upload flow:
You upload file.pdf
↓
S3 sends request to AWS KMS: "Give me a data encryption key"
↓
KMS generates a data key (encrypted + plaintext versions)
↓
S3 uses plaintext key to encrypt your file
↓
S3 stores: encrypted file + encrypted key (plaintext key discarded)
↓
Your file sits on disk: fully encrypted ✅
Download flow:
You request file.pdf
↓
S3 checks your IAM permissions (must have s3:GetObject)
↓
S3 checks your KMS permissions (must have kms:Decrypt)
↓
S3 sends encrypted key to KMS: "Decrypt this"
↓
KMS decrypts the key → returns plaintext key
↓
S3 uses plaintext key to decrypt your file
↓
You receive the original file ✅
Why SSE-KMS over SSE-S3?
- You control the KMS key (create, rotate, disable, delete)
- Full audit trail — every KMS key usage logged in CloudTrail
- Granular access control — you can restrict who can use the key via KMS key policy
- Required by many compliance standards (HIPAA, PCI-DSS, FedRAMP)
Downside: Every object upload/download = one KMS API call. KMS has a cost per API call and rate limits.
SSE-C (Server-Side Encryption with Customer-Provided Keys)
- You provide your own encryption key with each request
- AWS uses it to encrypt/decrypt, but never stores your key
- You are 100% responsible for key management
Upload: You send (file + your-key) → S3 encrypts → discards your-key
Download: You send (object-key + your-key) → S3 decrypts → returns file
Lose your key = lose your data permanently (no recovery)
Use when: You have strict regulatory requirements that AWS must never hold your encryption key even temporarily.
CSE (Client-Side Encryption)
- You encrypt the file before uploading it to S3
- S3 just stores whatever bytes you send — it has no idea the content is encrypted
- You manage all encryption/decryption on your side
Your app: file → encrypt locally → send encrypted bytes → S3 stores them
Your app: download encrypted bytes → decrypt locally → original file
AWS sees only ciphertext — never the original data
Use when: Maximum data confidentiality — even AWS employees cannot read your data.
Encryption Comparison
| Method | Who manages key | AWS sees plaintext? | Audit trail | Use when |
|---|---|---|---|---|
| SSE-S3 | AWS (automatic) | Internally yes | Basic | Default, no compliance needs |
| SSE-KMS | You (via KMS) | Internally yes | Full (CloudTrail) | Compliance, audit requirements |
| SSE-C | You | Never | None | Strict key control |
| CSE | You | Never | None | Maximum confidentiality |
S3 Bucket Keys — Reducing KMS Costs by 99%
With SSE-KMS, every single object upload/download triggers a KMS API call. For buckets with millions of objects, this generates millions of KMS calls — which have both a per-call cost and API rate limits.
S3 Bucket Keys solve this:
Without Bucket Key:
100 object uploads → 100 KMS API calls → $$$
With Bucket Key enabled:
S3 generates one bucket-level data key from KMS
That key is cached in S3 for a time period
100 object uploads → 1 KMS API call + 99 local operations → $ (99% fewer calls)
Analogy: Instead of going to the bank (KMS) to get cash for every purchase, you withdraw a stack of cash once and use it locally for many purchases.
- Reduces KMS API costs by up to 99%
- Reduces KMS request rate (avoids throttling)
- Transparent — encryption strength is unchanged
- Enable it when creating the bucket or in bucket properties
🎯 Cert tip: "A company uses SSE-KMS on an S3 bucket with millions of daily uploads and is hitting KMS rate limits. What's the fix?" → Enable S3 Bucket Keys.
AWS CloudTrail + S3 Audit Logging
CloudTrail records every API call made to AWS services — who did what, when, from where. For S3 with KMS encryption, it becomes your full audit trail.
What CloudTrail logs for S3 + KMS:
Who: IAM user/role that accessed the object
What: s3:GetObject, s3:PutObject, kms:Decrypt, kms:GenerateDataKey
When: Timestamp of the request
Where: Source IP address
Result: Success or failure (and why if failed)
Two types of S3 events in CloudTrail:
- Management Events — bucket-level operations (CreateBucket, DeleteBucket, PutBucketPolicy) — logged by default
- Data Events — object-level operations (GetObject, PutObject, DeleteObject) — must be explicitly enabled (generates high volume)
S3 Server Access Logging (separate from CloudTrail):
- Detailed request logs written directly to another S3 bucket
- Every HTTP request to the bucket: requester IP, request type, response code, bytes transferred
- Good for access pattern analysis and billing investigation
🎯 DevOps/Security use case: Enable CloudTrail data events for your S3 bucket to detect unauthorized access. If someone outside your organization downloads a sensitive file, CloudTrail has the full record — user, IP, timestamp, object key.
Bucket Policies — Object Ownership & ACL Settings
When multiple AWS accounts are involved (or when files are uploaded by external parties), there are three ownership models that control who owns uploaded objects and how ACLs work.
Three Object Ownership Settings
1. Bucket Owner Enforced (Recommended)
- All objects in the bucket are always owned by the bucket owner
- ACLs are completely disabled
- All access must be controlled via bucket policies or IAM policies only
- Simplest and most secure model
External account uploads file → bucket owner automatically owns it
No ACL confusion, no cross-account ownership issues
2. Bucket Owner Preferred
- Bucket owner takes ownership IF the uploader includes
bucket-owner-full-controlACL - ACLs are still enabled
- Gives flexibility while nudging toward central ownership
External account uploads with bucket-owner-full-control ACL
→ bucket owner gets ownership ✅
External account uploads without that ACL
→ external account retains ownership ⚠️
3. Object Writer
- The account that uploads an object owns it (legacy default behavior)
- Can cause situations where bucket owner cannot read/delete objects in their own bucket
- ACLs enabled
✅ Best practice: Always use Bucket Owner Enforced for new buckets. It's simpler, removes ACL complexity, and avoids cross-account ownership confusion that has caused real security incidents.
S3 Access Control — Full Picture
S3 has multiple layers of access control that work together:
Request to access S3 object
│
▼
1. IAM Policy — does the requester's IAM user/role allow this?
│
▼
2. Bucket Policy — does the bucket policy allow this request?
│
▼
3. S3 Block Public Access — is public access blocked at account/bucket level?
│
▼
4. ACL (if enabled) — does the object/bucket ACL permit access?
│
▼
Access Granted or Denied
All applicable policies are evaluated together. An explicit Deny at ANY layer = denied, regardless of allows elsewhere.
Bucket Policy
A JSON document attached to the bucket that defines who can access it and what they can do:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEC2Access",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:role/EC2-S3-Role"
},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
},
{
"Sid": "DenyDeleteForEveryone",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
S3 Block Public Access (4 Settings)
AWS added this specifically to prevent accidental data exposure — one of the most common cloud security incidents.
| Setting | What it blocks |
|---|---|
| Block public ACLs | Rejects PUT requests with public ACLs |
| Ignore public ACLs | Ignores existing public ACLs |
| Block public bucket policies | Rejects bucket policies that grant public access |
| Restrict public bucket policies | Restricts public access even if bucket policy allows it |
✅ Best practice: Enable all 4 Block Public Access settings at the account level. Explicitly disable only for specific buckets that genuinely need public access (e.g., static website hosting).
Explicit Deny — The Override Rule
This is one of the most important concepts in all of AWS security — and it applies everywhere, not just S3.
The rule:
Explicit Deny ALWAYS wins over any Allow, from any source
Policy evaluation order:
1. Is there an Explicit Deny anywhere? → DENIED (done, nothing else checked)
2. Is there an Explicit Allow? → ALLOWED
3. Neither? → DENIED (implicit deny is the default)
Why this matters in practice:
Scenario: User has AdministratorAccess (allows everything)
SCP says: Deny s3:DeleteBucket
Result: User CANNOT delete S3 buckets — despite having AdministratorAccess
The Deny wins. Always.
Real example in a bucket policy:
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::critical-data/*",
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
This denies all S3 actions if the request isn't using HTTPS — even if the user has full S3 permissions elsewhere. The Deny wins.
🎯 Interview tip: "A user has S3FullAccess but can't delete objects from a specific bucket. Why?" → There's likely an explicit Deny somewhere — in a bucket policy, SCP, or permission boundary. Explicit Deny always overrides explicit Allow.
VPC — Brief Introduction
VPC = Virtual Private Cloud — your own logically isolated private network inside AWS.
Think of it as buying a plot of land inside a large city (AWS). Within your plot, you decide the layout — which areas are public-facing, which are private, what gets connected to the internet.
Why it's relevant to S3 here: By default, S3 is accessed over the public internet (via HTTPS). For security, you can set up a VPC Endpoint for S3 — a private connection from your VPC directly to S3 without traffic leaving the AWS network.
Without VPC Endpoint:
EC2 → public internet → S3 (traffic exits AWS network)
With VPC Endpoint (Gateway type):
EC2 → VPC Endpoint → S3 (traffic stays inside AWS backbone)
No internet gateway needed
More secure, no data transfer costs for S3
VPC will be covered in depth in a dedicated session. For now: VPC = your private network, and S3 can be accessed privately through it via Gateway Endpoints.
S3 CLI Commands
# List all buckets
aws s3 ls
# List objects in a bucket
aws s3 ls s3://my-bucket/
aws s3 ls s3://my-bucket/reports/ --recursive
# Create a bucket
aws s3 mb s3://my-new-bucket-name --region ap-south-1
# Upload a file
aws s3 cp local-file.txt s3://my-bucket/uploads/file.txt
# Upload entire folder
aws s3 cp ./my-folder s3://my-bucket/my-folder/ --recursive
# Download a file
aws s3 cp s3://my-bucket/file.txt ./local-file.txt
# Sync local folder to S3 (only uploads new/changed files)
aws s3 sync ./local-folder s3://my-bucket/folder/
# Delete an object
aws s3 rm s3://my-bucket/old-file.txt
# Delete all objects in a folder
aws s3 rm s3://my-bucket/old-folder/ --recursive
# Delete a bucket (must be empty first)
aws s3 rb s3://my-bucket
# Delete bucket and all contents
aws s3 rb s3://my-bucket --force
# Copy between buckets (cross-region)
aws s3 cp s3://source-bucket/file.txt s3://dest-bucket/file.txt \
--source-region ap-south-1 --region us-east-1
# Set storage class on upload
aws s3 cp file.txt s3://my-bucket/ --storage-class STANDARD_IA
# Generate pre-signed URL (valid 1 hour)
aws s3 presign s3://my-bucket/private-file.pdf --expires-in 3600
# Put bucket policy
aws s3api put-bucket-policy \
--bucket my-bucket \
--policy file://policy.json
# Enable versioning
aws s3api put-bucket-versioning \
--bucket my-bucket \
--versioning-configuration Status=Enabled
boto3 — S3 with Python
import boto3
s3 = boto3.client('s3', region_name='ap-south-1')
s3_resource = boto3.resource('s3')
# ── List buckets ────────────────────────────────────────────
response = s3.list_buckets()
for bucket in response['Buckets']:
print(f"Bucket: {bucket['Name']} | Created: {bucket['CreationDate']}")
# ── Create bucket ───────────────────────────────────────────
s3.create_bucket(
Bucket='my-new-bucket-2026',
CreateBucketConfiguration={'LocationConstraint': 'ap-south-1'}
)
# ── Upload file ─────────────────────────────────────────────
s3.upload_file('local-file.txt', 'my-bucket', 'uploads/file.txt')
# ── Upload with SSE-KMS encryption ─────────────────────────
s3.upload_file(
'sensitive.pdf',
'my-bucket',
'secure/sensitive.pdf',
ExtraArgs={
'ServerSideEncryption': 'aws:kms',
'SSEKMSKeyId': 'arn:aws:kms:ap-south-1:123456789:key/abc-123'
}
)
# ── Download file ───────────────────────────────────────────
s3.download_file('my-bucket', 'uploads/file.txt', 'downloaded-file.txt')
# ── List objects in bucket ──────────────────────────────────
response = s3.list_objects_v2(Bucket='my-bucket', Prefix='reports/')
for obj in response.get('Contents', []):
print(f"Key: {obj['Key']} | Size: {obj['Size']} bytes")
# ── Delete object ───────────────────────────────────────────
s3.delete_object(Bucket='my-bucket', Key='old-file.txt')
# ── Generate pre-signed URL ─────────────────────────────────
url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': 'my-bucket', 'Key': 'private-doc.pdf'},
ExpiresIn=3600
)
print(f"Share this URL (valid 1 hour): {url}")
# ── Apply bucket policy ─────────────────────────────────────
import json
policy = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {"Bool": {"aws:SecureTransport": "false"}}
}]
}
s3.put_bucket_policy(Bucket='my-bucket', Policy=json.dumps(policy))
⚡ Quick Revision
JSON vs YAML
JSON = braces/quotes/commas, API responses, IAM policies
YAML = indentation/colons, CloudFormation, SAM
Both used for infrastructure-as-code — know both
API CALLS
GET = read/retrieve
PUT = create/replace
POST = create/trigger
DELETE = remove
HEAD = metadata only
S3 OBJECT URL
https://<bucket>.s3.<region>.amazonaws.com/<key>
Valid only while object exists and access is permitted
Pre-signed URL = temporary access for private objects
STORAGE CLASSES (cost vs speed)
Standard → frequent access, fastest, most expensive
Intelligent-Tiering → unknown patterns, auto-optimizes
Standard-IA → monthly access, fast retrieval fee
Glacier Instant → quarterly access, instant retrieval
Glacier Deep → annual access, 12hr retrieval, cheapest
ENCRYPTION
SSE-S3 → AWS manages key, default, zero config
SSE-KMS → you control key via KMS, audit trail, compliance
SSE-C → you provide key per request, AWS never stores it
CSE → you encrypt before upload, AWS sees ciphertext only
S3 BUCKET KEYS
Reduces KMS API calls by 99%
One bucket-level key cached, used for multiple objects
Enable for high-volume SSE-KMS buckets
BUCKET OWNERSHIP
Owner Enforced = ACLs off, bucket owner owns all (use this)
Owner Preferred = owner gets ownership if ACL included
Object Writer = uploader owns (legacy, avoid)
EXPLICIT DENY
Always overrides any Allow, from any source
Evaluation: Explicit Deny → Explicit Allow → Implicit Deny
CLOUDTRAIL
Logs every API call: who, what, when, where, result
Data events (object-level) must be explicitly enabled
Use for security auditing of S3 + KMS access
💼 Interview Questions
Q1: What is the difference between an S3 bucket and an S3 object?
A bucket is the container — like a top-level folder — created in a specific Region with a globally unique name. An object is the actual file stored inside the bucket, uniquely identified by its key (path). The bucket holds the object; the object is the data.
Q2: What makes an S3 object URL unique and when does it stop working?
The URL is formed from the bucket name, region, and object key — all of which are unique. It stops working when the object is deleted or when access permissions are revoked. For private objects, you need a pre-signed URL with an expiry time to share temporary access.
Q3: What is SSE-KMS and how does it work?
SSE-KMS uses AWS Key Management Service for encryption. On upload, S3 requests a data key from KMS, encrypts the file with the plaintext key, and stores both the encrypted file and the encrypted key (discarding the plaintext key). On download, S3 verifies IAM and KMS permissions, asks KMS to decrypt the encrypted key, then decrypts and returns the file. You control the KMS key and every usage is logged in CloudTrail.
Q4: What are S3 Bucket Keys and why would you enable them?
S3 Bucket Keys are a bucket-level data key cached in S3, used to encrypt multiple objects instead of calling KMS for every single operation. This reduces KMS API calls by up to 99%, significantly reducing cost and avoiding KMS rate limit throttling — essential for high-throughput S3 buckets using SSE-KMS.
Q5: What is the difference between SSE-KMS and SSE-C?
SSE-KMS means AWS generates and manages the data keys inside KMS — you control the master key but AWS handles the actual encryption. SSE-C means you provide your own encryption key with every request — AWS uses it and immediately discards it, never storing it. If you lose your SSE-C key, your data is gone forever.
Q6: An IAM user has S3FullAccess but cannot delete objects from a bucket. Why?
There's an explicit Deny somewhere overriding the Allow. It could be in the bucket policy, an SCP at the Organizations level, or a permission boundary. Explicit Deny always wins over any Allow, from any source.
Q7: What is Bucket Owner Enforced and why is it recommended?
It disables ACLs entirely and ensures the bucket owner always owns all objects regardless of who uploaded them. It's the simplest and most secure model — no ACL confusion, no cross-account ownership issues, and access is controlled purely through bucket policies and IAM.
Q8: Write a bucket policy that denies all HTTP (non-HTTPS) access.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"Bool": {"aws:SecureTransport": "false"}
}
}]
}
🔬 Practice Tasks
Create and configure a bucket: Create an S3 bucket with versioning enabled, Block Public Access on, and a lifecycle policy that moves objects to Standard-IA after 30 days and Glacier after 90 days.
Write bucket policies: Write a JSON bucket policy that: (a) allows your IAM user to read all objects, (b) allows an EC2 role to upload objects, and (c) denies all non-HTTPS requests.
SSE-KMS lab: Enable SSE-KMS on a bucket. Upload a file. Download it. Open CloudTrail and find the
kms:GenerateDataKeyandkms:Decryptevents corresponding to your upload and download.Pre-signed URL: Using boto3, upload a private file to S3, generate a pre-signed URL valid for 5 minutes, open it in your browser, wait 5 minutes, try again — confirm it expires.
CLI practice: Using AWS CLI, sync a local folder to S3, list the objects, update one file locally, sync again, and confirm only the changed file was re-uploaded (not everything).
Versioning test: Enable versioning on a bucket. Upload a file, modify it, upload again with the same key, upload a third time. List all versions. Retrieve v1. Delete the latest version and confirm v2 becomes current.
Write the JSON and YAML: Without looking at notes, write from scratch: (a) a CloudFormation YAML snippet that creates an S3 bucket with versioning, (b) an S3 bucket policy in JSON that makes all objects in
/public/*readable by everyone.
AWS Session 7 — S3 Essentials | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer
Top comments (0)