Amazon EKS — Security Hands-On Lab

2 minute read

In this lab, you’ll configure IRSA, create RBAC roles, enable pod security, and mount secrets from AWS Secrets Manager.


Prerequisites

  • Running EKS cluster
  • kubectl, aws CLI, eksctl, helm configured
  • AWS Secrets Manager access

Step 1 — Enable OIDC Provider

eksctl utils associate-iam-oidc-provider \
  --cluster my-cluster \
  --approve

Step 2 — Configure IRSA for S3 Access

Create a policy:

cat > s3-read-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": ["arn:aws:s3:::my-lab-bucket", "arn:aws:s3:::my-lab-bucket/*"]
    }
  ]
}
EOF

aws iam create-policy \
  --policy-name EKSLabS3ReadPolicy \
  --policy-document file://s3-read-policy.json

Create the IRSA ServiceAccount:

eksctl create iamserviceaccount \
  --cluster my-cluster \
  --namespace default \
  --name s3-reader-sa \
  --attach-policy-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/EKSLabS3ReadPolicy \
  --approve

Test:

kubectl run s3-test --image=amazon/aws-cli:latest \
  --serviceaccount=s3-reader-sa \
  --restart=Never \
  --command -- aws s3 ls s3://my-lab-bucket/

kubectl logs s3-test

Step 3 — Create RBAC for a Dev Team

# dev-rbac.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dev-team
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-deployer
  namespace: dev-team
rules:
  - apiGroups: ["", "apps"]
    resources: ["pods", "deployments", "services", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-deployer-binding
  namespace: dev-team
subjects:
  - kind: Group
    name: dev-group
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-deployer
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f dev-rbac.yaml

# Test with impersonation
kubectl auth can-i create deployments -n dev-team --as-group=dev-group --as=dev-user
# yes

kubectl auth can-i delete namespaces --as-group=dev-group --as=dev-user
# no

Step 4 — Enable Pod Security Standards

kubectl label namespace dev-team \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/warn=restricted

Test — this pod should be rejected:

kubectl run privileged-test --image=nginx:latest \
  -n dev-team \
  --overrides='{
    "spec": {
      "containers": [{
        "name": "test",
        "image": "nginx:latest",
        "securityContext": {"privileged": true}
      }]
    }
  }'
# Error: violates PodSecurity "restricted"

This compliant pod should work:

# secure-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  namespace: dev-team
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: nginx:latest
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: cache
          mountPath: /var/cache/nginx
        - name: run
          mountPath: /var/run
  volumes:
    - name: tmp
      emptyDir: {}
    - name: cache
      emptyDir: {}
    - name: run
      emptyDir: {}
kubectl apply -f secure-pod.yaml

Step 5 — Mount Secrets from Secrets Manager

Create a secret in AWS:

aws secretsmanager create-secret \
  --name lab/db-credentials \
  --secret-string '{"username":"admin","password":"SuperSecret123"}'

Install ASCP:

helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver \
  --namespace kube-system --set syncSecret.enabled=true

kubectl apply -f https://raw.githubusercontent.com/aws/secrets-store-csi-driver-provider-aws/main/deployment/aws-provider-installer.yaml

Create IRSA for Secrets Manager access:

cat > sm-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
      "Resource": ["arn:aws:secretsmanager:us-east-1:*:secret:lab/*"]
    }
  ]
}
EOF

aws iam create-policy --policy-name EKSLabSMPolicy --policy-document file://sm-policy.json

eksctl create iamserviceaccount \
  --cluster my-cluster \
  --namespace dev-team \
  --name sm-reader-sa \
  --attach-policy-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/EKSLabSMPolicy \
  --approve

Create SecretProviderClass + Pod:

# secret-mount.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: lab-db-creds
  namespace: dev-team
spec:
  provider: aws
  parameters:
    objects: |
      - objectName: "lab/db-credentials"
        objectType: "secretsmanager"
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-consumer
  namespace: dev-team
spec:
  serviceAccountName: sm-reader-sa
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: busybox:latest
      command: ["sh", "-c", "cat /mnt/secrets/lab/db-credentials && sleep 3600"]
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      volumeMounts:
        - name: secrets
          mountPath: /mnt/secrets
          readOnly: true
  volumes:
    - name: secrets
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: lab-db-creds
kubectl apply -f secret-mount.yaml
kubectl logs secret-consumer -n dev-team
# {"username":"admin","password":"SuperSecret123"}

Cleanup

kubectl delete namespace dev-team
kubectl delete pod s3-test
eksctl delete iamserviceaccount --cluster my-cluster --namespace default --name s3-reader-sa
aws secretsmanager delete-secret --secret-id lab/db-credentials --force-delete-without-recovery
helm uninstall csi-secrets-store -n kube-system

Updated: