You are viewing a preview of this lesson. Sign in to start learning
Back to Mastering AWS

AWS Foundations & Core Services

Master cloud fundamentals, global infrastructure, identity management, and networking basics that form the foundation of all AWS workloads

AWS Foundations & Core Services

Master AWS cloud fundamentals with free flashcards and spaced repetition practice. This lesson covers core AWS services including EC2, S3, and IAM, essential identity and access management concepts, and the AWS global infrastructureβ€”foundational knowledge for cloud architects, developers, and anyone preparing for AWS certification exams.

Welcome to AWS Foundations πŸš€

Amazon Web Services (AWS) is the world's most comprehensive and widely adopted cloud platform, offering over 200 fully featured services from data centers globally. Whether you're building a startup, migrating enterprise workloads, or exploring cloud computing for the first time, understanding AWS foundations is your gateway to modern infrastructure.

In this lesson, we'll explore the core services that form the backbone of AWS: compute power through EC2, storage via S3, security with IAM, networking fundamentals, and the global infrastructure that makes it all possible. By the end, you'll understand how these services interconnect and how to architect basic cloud solutions.

πŸ’‘ Pro Tip: AWS follows a pay-as-you-go pricing model. Understanding service fundamentals helps you optimize costs from day one!


Core Concepts: AWS Global Infrastructure 🌍

Before diving into specific services, let's understand how AWS is physically organized:

Regions, Availability Zones, and Edge Locations

AWS GLOBAL INFRASTRUCTURE

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 AWS REGION                      β”‚
β”‚  (e.g., us-east-1, eu-west-1)                  β”‚
β”‚                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚  β”‚ Availability β”‚  β”‚ Availability β”‚           β”‚
β”‚  β”‚   Zone A     β”‚  β”‚   Zone B     β”‚  ← Multipleβ”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚    AZs    β”‚
β”‚  β”‚  β”‚Data    β”‚  β”‚  β”‚  β”‚Data    β”‚  β”‚           β”‚
β”‚  β”‚  β”‚Centers β”‚  β”‚  β”‚  β”‚Centers β”‚  β”‚           β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚
β”‚         ↕                  ↕                    β”‚
β”‚   Low-latency links (< 2ms)                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        ↓                            ↓
   Edge Locations              CloudFront CDN
   (200+ worldwide)            (Content Delivery)

Regions are geographic areas containing multiple isolated data centers. Each region is completely independent, allowing you to:

  • Build disaster recovery across continents
  • Comply with data residency requirements
  • Reduce latency by deploying close to users

Availability Zones (AZs) are one or more discrete data centers within a region, each with redundant power, networking, and connectivity. They're:

  • Physically separated (different buildings, flood plains)
  • Connected via low-latency, high-throughput networking
  • The foundation for high availability architecture

Edge Locations are endpoints for AWS CloudFront (CDN) that cache content closer to users, reducing latency for content delivery.

🧠 Memory Device: Think R-A-E: Regions contain Availability Zones, which connect to Edge locations.


Amazon EC2: Elastic Compute Cloud πŸ’»

EC2 provides resizable compute capacity in the cloudβ€”virtual servers you can launch in minutes. It's the workhorse of AWS compute services.

Instance Types and Use Cases

EC2 instances come in families optimized for different workloads:

FamilyTypeOptimized ForUse Case
General Purposet3, t4g, m5Balanced CPU/MemoryWeb servers, small databases
Compute Optimizedc5, c6gHigh-performance processorsScientific modeling, gaming servers
Memory Optimizedr5, x2Large datasets in memoryIn-memory databases, big data analytics
Storage Optimizedi3, d2High sequential I/ONoSQL databases, data warehousing
Accelerated Computingp4, g4GPU workloadsMachine learning, video encoding

🧠 Mnemonic for families: "GCMSA" β†’ General, Compute, Memory, Storage, Accelerated

EC2 Purchasing Options

PRICING MODEL COMPARISON

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  On-Demand πŸ’΅    β”‚  β”‚  Reserved πŸ“…     β”‚  β”‚  Spot πŸ’°         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ Pay per hour   β”‚  β”‚ β€’ 1-3 year term  β”‚  β”‚ β€’ Unused capacityβ”‚
β”‚ β€’ No commitment  β”‚  β”‚ β€’ Up to 75% off  β”‚  β”‚ β€’ Up to 90% off  β”‚
β”‚ β€’ Full price     β”‚  β”‚ β€’ Predictable    β”‚  β”‚ β€’ Can terminate  β”‚
β”‚ β€’ Flexibility    β”‚  β”‚   workloads      β”‚  β”‚ β€’ Fault-tolerant β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Savings Plans🎯 β”‚  β”‚  Dedicated 🏒    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ Hourly commit  β”‚  β”‚ β€’ Physical serverβ”‚
β”‚ β€’ Up to 72% off  β”‚  β”‚ β€’ Compliance     β”‚
β”‚ β€’ Flexible       β”‚  β”‚ β€’ Licensing      β”‚
β”‚   instance type  β”‚  β”‚ β€’ Most expensive β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’‘ Cost Optimization Tip: Use Reserved Instances or Savings Plans for steady-state workloads, Spot Instances for batch processing, and On-Demand for unpredictable spikes.


Amazon S3: Simple Storage Service πŸ—„οΈ

S3 is object storage built to store and retrieve any amount of data from anywhere. It's one of AWS's foundational services, offering 11 nines of durability (99.999999999%).

S3 Core Concepts

Buckets are containers for objects (files). Each bucket:

  • Has a globally unique name
  • Exists in a specific AWS region
  • Can store unlimited objects
  • Supports versioning, encryption, and lifecycle policies

Objects consist of:

  • Key: The filename/path (e.g., documents/report.pdf)
  • Value: The actual data (up to 5TB per object)
  • Metadata: Key-value pairs describing the object
  • Version ID: If versioning is enabled

S3 Storage Classes

Storage ClassUse CaseRetrieval TimeCost
S3 StandardFrequently accessed dataMilliseconds$$$$
S3 Intelligent-TieringUnknown access patternsMillisecondsAuto-optimized
S3 Standard-IAInfrequently accessedMilliseconds$$$
S3 One Zone-IARecreatable, infrequentMilliseconds$$
S3 Glacier InstantArchive, instant accessMilliseconds$$
S3 Glacier FlexibleArchive, rare accessMinutes-hours$
S3 Glacier Deep ArchiveLong-term archive12 hoursΒ’

S3 Request Structure

Here's how you interact with S3 programmatically:

import boto3

## Create S3 client
s3 = boto3.client('s3')

## Upload object
s3.put_object(
    Bucket='my-bucket',
    Key='folder/file.txt',
    Body=b'Hello AWS!',
    ServerSideEncryption='AES256'
)

## Download object
response = s3.get_object(
    Bucket='my-bucket',
    Key='folder/file.txt'
)
data = response['Body'].read()

πŸ”’ Security Best Practice: Always enable versioning and MFA Delete for critical buckets to protect against accidental deletion.


AWS IAM: Identity and Access Management πŸ”

IAM controls who can access your AWS resources (authentication) and what they can do (authorization). It's the security foundation of AWS.

IAM Core Components

IAM HIERARCHY

        AWS Account (Root)
               β”‚
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”
       ↓               ↓
    Users πŸ‘€        Groups πŸ‘₯
       β”‚               β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
               ↓
          Policies πŸ“œ
               β”‚
               ↓
         Permissions βœ“/βœ—
               β”‚
               ↓
         AWS Resources πŸ—„οΈπŸ’»πŸŒ

Roles 🎭 β†’ Temporary credentials
          for services/federated users

Users represent individual people or services with long-term credentials (username/password, access keys).

Groups are collections of users with shared permissions. Users inherit all group policies.

Roles provide temporary security credentials. Unlike users, roles:

  • Have no permanent credentials
  • Are assumed by trusted entities
  • Are ideal for EC2 instances, Lambda functions, cross-account access

Policies are JSON documents defining permissions:

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

IAM Policy Evaluation Logic

POLICY EVALUATION FLOW

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Request made     β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             ↓
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Explicit DENY?   │───YES──→ ❌ DENY
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚ NO
             ↓
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Explicit ALLOW?  │───NO───→ ❌ DENY
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        (default)
             β”‚ YES
             ↓
          βœ… ALLOW

πŸ”΄ Explicit Deny always wins!

πŸ’‘ Principle of Least Privilege: Grant only the permissions required to perform a task. Start with minimum permissions and add as needed.

Using IAM Roles with EC2

import boto3

## EC2 instance with attached IAM role
## automatically uses role credentials
s3 = boto3.client('s3')

## No need to hardcode access keys!
## Role credentials are temporary and auto-rotated
buckets = s3.list_buckets()
for bucket in buckets['Buckets']:
    print(bucket['Name'])

⚠️ Security Warning: Never hardcode AWS access keys in application code! Always use IAM roles for EC2/Lambda or environment variables with strict access controls.


Amazon VPC: Virtual Private Cloud 🌐

VPC lets you provision a logically isolated section of AWS where you launch resources in a virtual network you define.

VPC Architecture

VPC STRUCTURE (CIDR: 10.0.0.0/16)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  VPC                            β”‚
β”‚                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Public Subnet      β”‚  β”‚ Private Subnet   β”‚  β”‚
β”‚  β”‚ 10.0.1.0/24       β”‚  β”‚ 10.0.2.0/24     β”‚  β”‚
β”‚  β”‚                    β”‚  β”‚                  β”‚  β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”     β”‚  β”‚ β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”   β”‚  β”‚
β”‚  β”‚ β”‚EC2 β”‚ β”‚EC2 β”‚     β”‚  β”‚ β”‚RDS β”‚ β”‚EC2 β”‚   β”‚  β”‚
β”‚  β”‚ β”‚Web β”‚ β”‚Web β”‚     β”‚  β”‚ β”‚DB  β”‚ β”‚App β”‚   β”‚  β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜     β”‚  β”‚ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜   β”‚  β”‚
β”‚  β”‚    ↓               β”‚  β”‚    ↑             β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚       ↓                        ↑                β”‚
β”‚  Internet Gateway         NAT Gateway          β”‚
β”‚       ↕                        ↕                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        ↓                        ↓
    Internet 🌍           Outbound only
                          (updates, patches)

Subnets divide your VPC's IP address range:

  • Public subnets: Route to Internet Gateway, get public IPs
  • Private subnets: No direct internet access, use NAT Gateway for outbound

Security Groups act as virtual firewalls at the instance level:

import boto3

ec2 = boto3.client('ec2')

## Create security group allowing HTTP/HTTPS
response = ec2.create_security_group(
    GroupName='web-server-sg',
    Description='Allow HTTP/HTTPS inbound',
    VpcId='vpc-12345678'
)

sg_id = response['GroupId']

## Add inbound rules
ec2.authorize_security_group_ingress(
    GroupId=sg_id,
    IpPermissions=[
        {
            'IpProtocol': 'tcp',
            'FromPort': 80,
            'ToPort': 80,
            'IpRanges': [{'CidrIp': '0.0.0.0/0'}]
        },
        {
            'IpProtocol': 'tcp',
            'FromPort': 443,
            'ToPort': 443,
            'IpRanges': [{'CidrIp': '0.0.0.0/0'}]
        }
    ]
)

Network ACLs (NACLs) provide subnet-level security:

  • Stateless (must define inbound AND outbound rules)
  • Evaluated in numbered order
  • Default NACL allows all traffic
FeatureSecurity GroupNetwork ACL
LevelInstanceSubnet
StatefulnessStateful (return traffic auto-allowed)Stateless (explicit rules needed)
RulesAllow rules onlyAllow and Deny rules
EvaluationAll rules evaluatedRules in number order

Example 1: Launching a Web Server on EC2 🌐

Let's walk through deploying a simple web application:

import boto3
import base64

ec2 = boto3.resource('ec2')

## User data script to install and start web server
user_data_script = """#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from AWS EC2!</h1>" > /var/www/html/index.html
"""

## Launch EC2 instance
instance = ec2.create_instances(
    ImageId='ami-0c55b159cbfafe1f0',  # Amazon Linux 2
    InstanceType='t3.micro',
    KeyName='my-key-pair',
    MinCount=1,
    MaxCount=1,
    SecurityGroupIds=['sg-0123456789abcdef0'],
    SubnetId='subnet-12345678',
    UserData=user_data_script,
    IamInstanceProfile={
        'Name': 'EC2-S3-Read-Role'
    },
    TagSpecifications=[
        {
            'ResourceType': 'instance',
            'Tags': [
                {'Key': 'Name', 'Value': 'WebServer-01'},
                {'Key': 'Environment', 'Value': 'Production'}
            ]
        }
    ]
)[0]

print(f"Instance launched: {instance.id}")
print(f"State: {instance.state['Name']}")

What's happening here:

  1. User Data runs scripts at instance launch (installing Apache)
  2. IAM Instance Profile attaches a role for AWS API access
  3. Security Group controls inbound/outbound traffic
  4. Tags organize and identify resources

πŸ”§ Try this: After launching, wait 2-3 minutes for initialization, then visit the instance's public IP in a browser!


Example 2: S3 Static Website Hosting πŸ–ΌοΈ

S3 can host static websites without any servers:

import boto3
import json

s3 = boto3.client('s3')
bucket_name = 'my-website-bucket-unique-12345'

## Create bucket
s3.create_bucket(Bucket=bucket_name)

## Enable static website hosting
s3.put_bucket_website(
    Bucket=bucket_name,
    WebsiteConfiguration={
        'IndexDocument': {'Suffix': 'index.html'},
        'ErrorDocument': {'Key': 'error.html'}
    }
)

## Make bucket publicly readable
bucket_policy = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": f"arn:aws:s3:::{bucket_name}/*"
        }
    ]
}

s3.put_bucket_policy(
    Bucket=bucket_name,
    Policy=json.dumps(bucket_policy)
)

## Upload index.html
html_content = """
<!DOCTYPE html>
<html>
<head><title>My AWS Website</title></head>
<body>
    <h1>Welcome to AWS S3 Static Hosting!</h1>
    <p>This site is served directly from S3.</p>
</body>
</html>
"""

s3.put_object(
    Bucket=bucket_name,
    Key='index.html',
    Body=html_content,
    ContentType='text/html'
)

website_url = f"http://{bucket_name}.s3-website-us-east-1.amazonaws.com"
print(f"Website available at: {website_url}")

πŸ’‘ Use Case: Perfect for single-page applications, documentation sites, or marketing landing pages. Costs pennies per month!


Example 3: Cross-Service Integration - EC2 + S3 + IAM πŸ”—

Here's a real-world pattern: EC2 instances processing files from S3:

import boto3
import json

iam = boto3.client('iam')

## Step 1: Create IAM policy allowing S3 access
policy_document = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::data-processing-bucket",
                "arn:aws:s3:::data-processing-bucket/*"
            ]
        }
    ]
}

policy_response = iam.create_policy(
    PolicyName='EC2-S3-Processing-Policy',
    PolicyDocument=json.dumps(policy_document)
)

policy_arn = policy_response['Policy']['Arn']

## Step 2: Create IAM role for EC2
trust_policy = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {"Service": "ec2.amazonaws.com"},
            "Action": "sts:AssumeRole"
        }
    ]
}

role_response = iam.create_role(
    RoleName='EC2-Data-Processor-Role',
    AssumeRolePolicyDocument=json.dumps(trust_policy)
)

## Step 3: Attach policy to role
iam.attach_role_policy(
    RoleName='EC2-Data-Processor-Role',
    PolicyArn=policy_arn
)

## Step 4: Create instance profile
iam.create_instance_profile(
    InstanceProfileName='EC2-Data-Processor-Profile'
)

iam.add_role_to_instance_profile(
    InstanceProfileName='EC2-Data-Processor-Profile',
    RoleName='EC2-Data-Processor-Role'
)

print("βœ… IAM setup complete!")
print("Attach 'EC2-Data-Processor-Profile' to EC2 instances")

Now on your EC2 instance:

## This code runs on EC2 with the attached role
import boto3

s3 = boto3.client('s3')

## No credentials needed - role provides temporary credentials!
response = s3.list_objects_v2(
    Bucket='data-processing-bucket',
    Prefix='incoming/'
)

for obj in response.get('Contents', []):
    key = obj['Key']
    
    # Download file
    s3.download_file(
        'data-processing-bucket',
        key,
        f'/tmp/{key.split("/")[-1]}'
    )
    
    # Process file (your logic here)
    processed_data = process_file(f'/tmp/{key.split("/")[-1]}')
    
    # Upload result
    s3.upload_file(
        f'/tmp/processed_{key.split("/")[-1]}',
        'data-processing-bucket',
        f'processed/{key.split("/")[-1]}'
    )

🧠 Architecture Pattern: This demonstrates the principle of least privilege and secure credential managementβ€”two AWS security pillars.


Example 4: VPC with Public and Private Subnets πŸ—οΈ

Building a production-ready network architecture:

import boto3

ec2 = boto3.client('ec2')

## Create VPC
vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16')
vpc_id = vpc['Vpc']['VpcId']

ec2.create_tags(
    Resources=[vpc_id],
    Tags=[{'Key': 'Name', 'Value': 'Production-VPC'}]
)

## Enable DNS hostnames
ec2.modify_vpc_attribute(
    VpcId=vpc_id,
    EnableDnsHostnames={'Value': True}
)

## Create Internet Gateway
igw = ec2.create_internet_gateway()
igw_id = igw['InternetGateway']['InternetGatewayId']
ec2.attach_internet_gateway(InternetGatewayId=igw_id, VpcId=vpc_id)

## Create public subnet
public_subnet = ec2.create_subnet(
    VpcId=vpc_id,
    CidrBlock='10.0.1.0/24',
    AvailabilityZone='us-east-1a'
)
public_subnet_id = public_subnet['Subnet']['SubnetId']

## Create private subnet
private_subnet = ec2.create_subnet(
    VpcId=vpc_id,
    CidrBlock='10.0.2.0/24',
    AvailabilityZone='us-east-1a'
)
private_subnet_id = private_subnet['Subnet']['SubnetId']

## Create route table for public subnet
public_rt = ec2.create_route_table(VpcId=vpc_id)
public_rt_id = public_rt['RouteTable']['RouteTableId']

## Add route to Internet Gateway
ec2.create_route(
    RouteTableId=public_rt_id,
    DestinationCidrBlock='0.0.0.0/0',
    GatewayId=igw_id
)

## Associate route table with public subnet
ec2.associate_route_table(
    RouteTableId=public_rt_id,
    SubnetId=public_subnet_id
)

## Allocate Elastic IP for NAT Gateway
eip = ec2.allocate_address(Domain='vpc')
allocation_id = eip['AllocationId']

## Create NAT Gateway in public subnet
nat_gw = ec2.create_nat_gateway(
    SubnetId=public_subnet_id,
    AllocationId=allocation_id
)
nat_gw_id = nat_gw['NatGateway']['NatGatewayId']

print(f"βœ… VPC created: {vpc_id}")
print(f"βœ… Public Subnet: {public_subnet_id}")
print(f"βœ… Private Subnet: {private_subnet_id}")
print(f"βœ… NAT Gateway: {nat_gw_id}")
print("\n⏳ Wait ~5 minutes for NAT Gateway to become available...")

Result: A production-ready network where:

  • Web servers in public subnet receive internet traffic
  • Application/database servers in private subnet remain isolated
  • Private instances can download updates via NAT Gateway

Common Mistakes to Avoid ⚠️

1. Hardcoding AWS Credentials

❌ Wrong:

import boto3

s3 = boto3.client(
    's3',
    aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
    aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
)

βœ… Right:

import boto3

## Use IAM role (EC2/Lambda) or environment variables
s3 = boto3.client('s3')
## Credentials automatically discovered via:
## 1. IAM role (preferred for EC2/Lambda/ECS)
## 2. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
## 3. ~/.aws/credentials file (local development)

Why it matters: Hardcoded credentials get committed to version control, exposing your AWS account to compromise.


2. Overly Permissive Security Groups

❌ Wrong:

## Allowing SSH from anywhere
ec2.authorize_security_group_ingress(
    GroupId=sg_id,
    IpPermissions=[{
        'IpProtocol': 'tcp',
        'FromPort': 22,
        'ToPort': 22,
        'IpRanges': [{'CidrIp': '0.0.0.0/0'}]  # ENTIRE INTERNET!
    }]
)

βœ… Right:

## Restrict to your office/home IP
ec2.authorize_security_group_ingress(
    GroupId=sg_id,
    IpPermissions=[{
        'IpProtocol': 'tcp',
        'FromPort': 22,
        'ToPort': 22,
        'IpRanges': [{'CidrIp': '203.0.113.5/32'}]  # Specific IP
    }]
)

Why it matters: Open SSH ports are constantly scanned and targeted by automated attacks. Limit access to known IPs or use AWS Systems Manager Session Manager instead.


3. Not Using IAM Roles for EC2

❌ Wrong:

## Storing credentials on EC2 instance
import boto3
import os

os.environ['AWS_ACCESS_KEY_ID'] = 'AKIAIOSFODNN7EXAMPLE'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCY'

s3 = boto3.client('s3')

βœ… Right:

## Attach IAM role to EC2 instance at launch
import boto3

ec2 = boto3.resource('ec2')

instance = ec2.create_instances(
    ImageId='ami-0c55b159cbfafe1f0',
    InstanceType='t3.micro',
    IamInstanceProfile={'Name': 'EC2-S3-Access-Role'},  # Role attached
    # ... other parameters
)[0]

## On the instance, credentials are automatic
s3 = boto3.client('s3')  # No credentials needed!

Why it matters: IAM roles provide temporary, automatically rotating credentials that can't be extracted or stolen.


4. Forgetting to Stop/Terminate Unused Instances

❌ Problem: Leaving EC2 instances running 24/7 when only needed occasionally.

βœ… Solution:

import boto3
from datetime import datetime

ec2 = boto3.client('ec2')

## Tag instances with usage schedule
ec2.create_tags(
    Resources=['i-1234567890abcdef0'],
    Tags=[
        {'Key': 'Schedule', 'Value': 'weekdays-9to5'},
        {'Key': 'Auto-Stop', 'Value': 'true'}
    ]
)

## Use Lambda + EventBridge to auto-stop/start
## Or manually stop when not needed:
ec2.stop_instances(InstanceIds=['i-1234567890abcdef0'])

Why it matters: Running a t3.medium 24/7 costs $30/month. Stopping it nights/weekends saves 70%+ ($9/month).


5. Single Availability Zone Deployments

❌ Wrong:

## All resources in one AZ
ec2.create_instances(
    SubnetId='subnet-12345678',  # us-east-1a only
    # ...
)

βœ… Right:

## Deploy across multiple AZs
subnets = ['subnet-12345678', 'subnet-87654321']  # Different AZs

for i, subnet in enumerate(subnets):
    ec2.create_instances(
        SubnetId=subnet,
        # ...
        Tags=[{'Key': 'Name', 'Value': f'WebServer-AZ{i+1}'}]
    )

## Use Elastic Load Balancer to distribute traffic
elbv2 = boto3.client('elbv2')
elbv2.create_load_balancer(
    Name='web-alb',
    Subnets=subnets,  # Spans both AZs
    # ...
)

Why it matters: Single AZ = single point of failure. AWS guarantees 99.99% uptime for Multi-AZ deployments vs 99.5% for Single-AZ.


6. Not Using Tags for Resource Management

❌ Problem: Hundreds of resources with names like "instance-1", "instance-2"...

βœ… Solution:

## Comprehensive tagging strategy
standard_tags = [
    {'Key': 'Environment', 'Value': 'Production'},
    {'Key': 'Application', 'Value': 'WebApp'},
    {'Key': 'Owner', 'Value': 'engineering-team'},
    {'Key': 'CostCenter', 'Value': 'CC-1234'},
    {'Key': 'ManagedBy', 'Value': 'Terraform'},
    {'Key': 'DataClassification', 'Value': 'Confidential'}
]

ec2.create_instances(
    # ...
    TagSpecifications=[{
        'ResourceType': 'instance',
        'Tags': standard_tags + [{'Key': 'Name', 'Value': 'WebServer-Prod-01'}]
    }]
)

Why it matters: Tags enable:

  • Cost allocation and billing reports
  • Automated backup policies
  • Security auditing
  • Resource search and filtering

Key Takeaways 🎯

πŸ“‹ Quick Reference Card

ServicePurposeKey Feature
EC2Virtual serversResizable compute capacity
S3Object storage11 nines durability
IAMAccess controlUsers, groups, roles, policies
VPCPrivate networkIsolated cloud resources

Essential Concepts:

  • Region: Geographic area with multiple AZs
  • Availability Zone: Isolated data center within region
  • Security Group: Stateful firewall at instance level
  • IAM Role: Temporary credentials for services
  • Subnet: IP range subdivision within VPC

Security Fundamentals:

  1. βœ… Use IAM roles instead of access keys
  2. βœ… Enable MFA on root and admin accounts
  3. βœ… Apply principle of least privilege
  4. βœ… Restrict Security Group rules to specific IPs
  5. βœ… Enable CloudTrail for audit logging
  6. βœ… Encrypt data at rest (S3, EBS) and in transit (TLS)

Cost Optimization:

  • Use Reserved Instances/Savings Plans for predictable workloads (up to 75% savings)
  • Stop EC2 instances when not needed
  • Use S3 lifecycle policies to transition to cheaper storage classes
  • Enable S3 Intelligent-Tiering for unknown access patterns
  • Set billing alerts and budgets
  • Right-size instances (don't over-provision)

πŸ“š Further Study

  1. AWS Documentation - Getting Started: https://aws.amazon.com/getting-started/ - Official tutorials and hands-on labs for all core services

  2. AWS Well-Architected Framework: https://aws.amazon.com/architecture/well-architected/ - Best practices for building secure, high-performing, resilient, and efficient infrastructure

  3. AWS Training and Certification: https://aws.amazon.com/training/ - Free digital training courses and certification paths


πŸŽ“ You've completed AWS Foundations & Core Services! You now understand the building blocks of AWS cloud infrastructure. Practice these concepts by building a simple three-tier application: web servers in public subnets, application logic on EC2 with IAM roles, and data stored in S3. Next, explore managed services like RDS, Lambda, and CloudFront to reduce operational overhead.