We have built the network fortress (VPCs) and automated the deployment pipelines (CI/CD). But what happens inside the fortress?
If your automated pipeline deploys a perfectly engineered Apache Spark job, but that job accidentally uses an administrative credential that allows it to delete the entire AWS S3 Data Lake, you have a ticking time bomb.
Now, we master AWS IAM (Identity and Access Management) and the Principle of Least Privilege.
Scenario: You are deploying a collaborative file-sharing application using Streamlit so your colleagues can safely upload and retrieve internal documents.
The Problem: To let the Streamlit app save files to an AWS S3 bucket, a developer generates an AWS_ACCESS_KEY_ID and a SECRET_ACCESS_KEY for an IAM User, and hardcodes those keys into the app.py file.
The Disaster: The developer accidentally pushes the code to a public GitHub repository. Within 4 seconds, automated bots scrape the keys, log into your AWS account, and spin up $100,000 worth of EC2 instances to mine cryptocurrency.
Task: Give the application access to S3 without ever generating or storing a static password.
We ban long-lived static keys for internal applications. We use IAM Roles.
The Architecture: An IAM Role is an identity that does not have credentials (no password, no access keys). Instead, it relies on temporary, dynamically rotating credentials managed entirely by AWS.
The Workflow:
- The Creation: We use Terraform to create an IAM Role named
StreamlitAppRole. - The Attachment: We attach this role directly to the EC2 instance (or ECS container) that is hosting the Streamlit application.
- The Magic (Metadata Service): When the Python code calls
boto3.client('s3'), boto3 notices there are no keys in the code. It automatically reaches out to a hidden, local AWS IP address (the Instance Metadata Service). AWS verifies the EC2 instance is assigned theStreamlitAppRoleand silently hands the Python code a temporary token valid for exactly 1 hour.
Why this is best: There are no keys to leak. Even if a hacker steals the source code, they get nothing. The security is tied physically to the server running the code.
Scenario: You have an Apache Airflow DAG that triggers a daily Spark job. The job reads raw JSON from the Bronze S3 bucket, transforms it, and writes Parquet to the Silver S3 bucket.
The Problem: To make the job work quickly, the engineer attaches the managed AWS policy AmazonS3FullAccess to the Spark cluster's IAM Role.
The Disaster: A bug in the PySpark script causes a dataframe overwrite command to target the Bronze bucket instead of the Silver bucket. Because the Spark cluster has "Full Access," AWS allows the operation. The raw, immutable history of the company is permanently overwritten.
Task: Design a permission structure that allows the job to do its work, but physically prevents it from causing catastrophic damage.
We never use AWS Managed "Full Access" policies in production. We write explicit, granular JSON documents that strictly define what actions are allowed on what specific resources.
The Architecture:
An IAM Policy defines the Effect, Action, and Resource.
The Workflow (The Custom Policy): We write a Terraform policy specifically for this Spark cluster:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::enterprise-bronze-lake",
"arn:aws:s3:::enterprise-bronze-lake/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::enterprise-silver-lake/*"
}
]
}
Production Grade Architecture:
Look closely at the JSON. The Spark cluster is only allowed to GetObject (Read) from Bronze. It is completely blocked from PutObject or DeleteObject in Bronze. If the PySpark bug occurs again, AWS IAM physically intercepts the API call and throws an AccessDeniedException. The job crashes, but the data survives.
Scenario: You are orchestrating your infrastructure on Kubernetes (EKS). You have 50 different microservices running as Pods on the exact same physical EC2 worker node. One is an Apache Flink stream processor that needs access to a Kafka MSK cluster. Another is a public-facing web API that should have no database access.
The Problem: In Step 1, we learned to attach an IAM Role to the EC2 instance. But Kubernetes runs dozens of distinct apps on a single EC2 instance.
The Disaster: If you attach the FlinkKafkaRole to the underlying EC2 node, every single Pod on that node inherits the exact same permissions. If a hacker exploits a vulnerability in the public web API, they instantly gain full access to your internal Kafka streams because they are sharing the host's IAM identity.
Task: Design an architecture that grants AWS IAM permissions at the individual container level, completely isolating noisy neighbors on the same hardware.
We decouple the AWS identity from the physical hardware and attach it logically to the Kubernetes namespace using OpenID Connect (OIDC).
The Architecture:
- The Identity Provider: We configure the AWS IAM control plane to trust the Kubernetes cluster's internal OIDC token issuer.
- The Service Account: Inside Kubernetes, we create a logical object called a
ServiceAccountdedicated specifically to the Flink job. We annotate it with the ARN (Amazon Resource Name) of the specific AWS IAM Role. - The Token Injection: When the Flink Pod boots up, Kubernetes intercepts the launch. It automatically injects an encrypted JSON Web Token (JWT) directly into the Pod's file system and sets an environment variable (
AWS_WEB_IDENTITY_TOKEN_FILE).
The Workflow:
- When the Flink job tries to connect to AWS MSK (Kafka), the AWS SDK reads that injected token.
- It trades the Kubernetes JWT for a temporary AWS security token.
- The Result: The Flink Pod gets the
FlinkKafkaRole. The Web API Pod right next to it gets absolutely nothing. You have achieved perfect, container-level isolation within a multi-tenant cluster.