S3 Identity Shift: Leveling Up to IAM Roles in K8s
Luke Hebert
- 4 minutes read - 706 wordsThe AWS S3 CSI driver lets you mount S3 buckets (or any S3-compatible object store) as volumes inside Kubernetes pods. This guide walks through the key components, installation, IAM role setup, and practical examples for provisioning S3-backed storage via StorageClasses, PersistentVolumeClaims, and direct CSI volume mounts.
Key Components
- Secret: Stores S3 credentials securely
- StorageClass: Defines how S3 volumes should be provisioned using the S3 CSI driver
- PersistentVolumeClaim: Requests storage from the S3 StorageClass
- Pod/Deployment: Shows how to mount and use the S3 storage
Note
- CSI Driver Required: You need to install the AWS S3 CSI driver in your cluster first.
- Credentials: Replace the placeholder credentials with your actual S3 IAM role.
- Bucket Configuration: Update the bucket name and region to match your S3 setup.
- Endpoint: Use the appropriate S3 endpoint (AWS S3, MinIO, etc.).
Installing the S3 CSI Driver
kubectl apply -k "github.com/kubernetes-sigs/aws-s3-csi-driver/deploy/kubernetes/overlays/stable/?ref=main"Alternative Approaches
- Use the AWS EFS CSI driver if you need true shared filesystem semantics.
- Consider using S3 via application-level SDKs rather than filesystem mounts for better performance.
- For object storage access patterns, direct API calls might be more appropriate than filesystem mounting.
The manifest shows both PVC-based and direct CSI volume mounting approaches, plus a deployment example for scalable applications.
Setting Up IAM Roles for S3 Access
1. Create IAM Policy
First, create an IAM policy for S3 access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}2. Create the IAM Role
Using the AWS CLI:
# Get your EKS cluster OIDC issuer URL
aws eks describe-cluster --name your-cluster-name --query "cluster.identity.oidc.issuer" --output text
# Create trust relationship policy
cat > trust-relationship.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.region.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.region.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:s3-service-account",
"oidc.eks.region.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF
# Create the role
aws iam create-role --role-name S3AccessRole --assume-role-policy-document file://trust-relationship.json# Attach the policy
aws iam attach-role-policy --role-name S3AccessRole --policy-arn arn:aws:iam::123456789012:policy/S3AccessPolicy3. Enable OIDC Provider (if not already done)
eksctl utils associate-iam-oidc-provider --cluster your-cluster-name --approveKey Benefits of Using IAM Roles
- No Hardcoded Secrets: No access keys stored in Kubernetes.
- Automatic Credential Rotation: AWS handles token refresh.
- Fine-Grained Permissions: Role-based access control.
- Audit Trail: CloudTrail logs all API calls.
- Secure: Credentials are temporary and scoped.
How It Works
- The ServiceAccount is annotated with the IAM role ARN.
- Pods using this ServiceAccount automatically get AWS credentials.
- AWS SDK/CLI in containers automatically uses these credentials.
- No manual credential management required.
Example
The updated manifest uses IAM roles instead of hardcoded access keys.
# Service Account with IAM role annotation
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-service-account
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/S3AccessRole---
# Storage Class for S3
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: s3-storage-class
provisioner: s3.csi.aws.com
parameters:
mounter: geesefs
bucket: "your-bucket-name"
region: "us-east-1"
endpoint: "https://s3.amazonaws.com"
# For non-AWS S3-compatible storage (like MinIO)
# endpoint: "http://minio-service:9000"
reclaimPolicy: Delete
allowVolumeExpansion: true---
# Persistent Volume Claim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: s3-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: s3-storage-class---
# Example Pod using the S3 storage
apiVersion: v1
kind: Pod
metadata:
name: s3-pod
namespace: default
spec:
serviceAccountName: s3-service-account
containers:
- name: app-container
image: nginx:latest
volumeMounts:
- name: s3-volume
mountPath: /data
# No need for explicit AWS credentials - using IAM role
volumes:
- name: s3-volume
persistentVolumeClaim:
claimName: s3-pvc---
# Alternative: Direct S3 volume mount (without PVC)
apiVersion: v1
kind: Pod
metadata:
name: s3-direct-pod
namespace: default
spec:
serviceAccountName: s3-service-account
containers:
- name: app-container
image: nginx:latest
volumeMounts:
- name: s3-direct-volume
mountPath: /s3data
# No need for explicit AWS credentials - using IAM role
volumes:
- name: s3-direct-volume
csi:
driver: s3.csi.aws.com
volumeAttributes:
bucket: "your-bucket-name"
region: "us-east-1"
# No secret needed - using IAM role via service account---
# Deployment example with S3 storage
apiVersion: apps/v1
kind: Deployment
metadata:
name: s3-app-deployment
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: s3-app
template:
metadata:
labels:
app: s3-app
spec:
serviceAccountName: s3-service-account
containers:
- name: app
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: s3-storage
mountPath: /var/www/html
# No need for explicit AWS credentials - using IAM role
volumes:
- name: s3-storage
persistentVolumeClaim:
claimName: s3-pvc