Home AWS EKS Security Best Practices: Kubernetes Container Security Monitoring Guide 2025
Post
Cancel

AWS EKS Security Best Practices: Kubernetes Container Security Monitoring Guide 2025

Introduction

Container adoption in enterprise environments has reached unprecedented scale, with the global container market projected to grow from $5.8 billion in 2023 to $15.1 billion by 2028. However, this explosive growth introduces complex security challenges that traditional security approaches cannot adequately address. AWS Elastic Kubernetes Service (EKS) and Fargate provide powerful platforms for container orchestration, but securing these environments requires a comprehensive DevSecOps approach that integrates security throughout the entire container lifecycle.

This comprehensive guide demonstrates how to implement enterprise-grade container security on AWS, focusing on EKS security best practices, automated threat detection, and DevSecOps pipeline integration. We’ll explore practical implementations using AWS security services, infrastructure as code, and automated compliance frameworks.

Current Container Security Landscape Statistics

  • 94% of organizations report using containers in production, yet 67% experienced container security incidents in 2023 (Sysdig 2023 Container Security Report)
  • Container vulnerabilities increased 150% year-over-year, with average remediation time of 287 days (Aqua Security State of Container Security)
  • AWS EKS adoption grew 76% in enterprise environments, making it the most deployed managed Kubernetes service (CNCF Annual Survey 2023)
  • DevSecOps implementation reduces security incident response time by 53% and vulnerability remediation by 41% (GitLab DevSecOps Report)
  • Misconfigured Kubernetes clusters account for 84% of container-related data breaches (Red Hat State of Kubernetes Security)

Container Security Fundamentals in AWS

Understanding the Container Attack Surface

Container security in AWS environments operates across multiple layers, each presenting unique attack vectors and security considerations:

Infrastructure Layer (AWS Account & VPC)

  • AWS account security and IAM policies
  • VPC network isolation and security groups
  • EKS cluster configuration and access controls
  • Node group security and auto-scaling configurations

Orchestration Layer (Kubernetes/EKS)

  • Kubernetes RBAC and service accounts
  • Pod security policies and standards
  • Network policies and service mesh security
  • Secrets management and encryption

Container Runtime Layer

  • Container runtime security (containerd/Docker)
  • Image scanning and vulnerability management
  • Runtime protection and anomaly detection
  • Resource limits and isolation

Application Layer

  • Application code vulnerabilities
  • Dependency management and supply chain security
  • Configuration management and secrets
  • Inter-service communication security

AWS Container Security Services Overview

AWS provides a comprehensive suite of security services specifically designed for container workloads:

1
2
3
4
5
# Core AWS container security services
aws eks describe-cluster --name production-cluster --query 'cluster.logging'
aws guardduty get-detector --detector-id $DETECTOR_ID
aws inspector2 describe-organization-configuration
aws securityhub describe-hub

AWS EKS Security Architecture

Cluster Security Foundation

Implementing security-first EKS cluster configuration using Infrastructure as Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# eks-cluster-security.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: secure-production-cluster
  region: us-west-2
  version: '1.28'

# Security-focused cluster configuration
cloudWatch:
  clusterLogging:
    enableTypes: ["audit", "authenticator", "controllerManager", "scheduler", "api"]
    logRetentionInDays: 30

# Enable private endpoint access
vpc:
  clusterEndpoints:
    privateAccess: true
    publicAccess: false  # Or restrict to specific CIDR blocks
    publicAccessCIDRs: ["10.0.0.0/8"]

# Encryption at rest
secretsEncryption:
  keyARN: arn:aws:kms:us-west-2:123456789:key/1234abcd-12ab-34cd-56ef-1234567890ab

# Network security
addons:
- name: vpc-cni
  version: latest
  configurationValues: |-
    {
      "env": {
        "ENABLE_POD_ENI": "true",
        "ENABLE_PREFIX_DELEGATION": "true"
      }
    }
- name: aws-ebs-csi-driver
  version: latest
- name: coredns
  version: latest

# Managed node groups with security optimizations
managedNodeGroups:
- name: secure-workers
  instanceType: m5.large
  minSize: 2
  maxSize: 10
  desiredCapacity: 3
  volumeSize: 100
  volumeEncrypted: true
  
  # Security group configuration
  securityGroups:
    withShared: true
    withLocal: true
    
  # Private subnets only
  privateNetworking: true
  
  # Enable SSM for secure access
  enableSsm: true
  
  # Instance metadata service configuration
  instanceMetadataOptions:
    httpTokens: required
    httpPutResponseHopLimit: 1
    
  # User data for additional hardening
  preBootstrapCommands:
    - /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource NodeGroupLaunchTemplate --region ${AWS::Region}
    - /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource NodeGroupASG --region ${AWS::Region}

Network Security Implementation

Implement comprehensive network security using AWS VPC CNI and Kubernetes network policies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# network-policies.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-to-aws-services
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to: []
    ports:
    - protocol: TCP
      port: 443  # HTTPS to AWS services
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system

Identity and Access Management (IAM) Integration

Implement fine-grained access control using IAM Roles for Service Accounts (IRSA):

1
2
3
4
5
6
7
8
9
10
11
12
13
# Create OIDC identity provider for the cluster
eksctl utils associate-iam-oidc-provider \
    --cluster secure-production-cluster \
    --approve

# Create service account with IAM role
eksctl create iamserviceaccount \
    --cluster=secure-production-cluster \
    --namespace=production \
    --name=app-service-account \
    --attach-policy-arn=arn:aws:iam::123456789:policy/ProductionAppPolicy \
    --override-existing-serviceaccounts \
    --approve
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::production-app-bucket/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": [
        "arn:aws:kms:us-west-2:123456789:key/app-encryption-key"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": [
        "arn:aws:secretsmanager:us-west-2:123456789:secret:production/app/*"
      ]
    }
  ]
}

Container Image Security and Supply Chain Protection

Automated Vulnerability Scanning Pipeline

Implement comprehensive image scanning using Amazon ECR and AWS Inspector:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/bin/bash
# container-security-scan.sh

set -euo pipefail

REPOSITORY_URI="123456789.dkr.ecr.us-west-2.amazonaws.com"
IMAGE_NAME="secure-app"
IMAGE_TAG="${GITHUB_SHA:-latest}"
FULL_IMAGE="${REPOSITORY_URI}/${IMAGE_NAME}:${IMAGE_TAG}"

# Enable enhanced scanning on ECR repository
aws ecr put-image-scanning-configuration \
    --repository-name $IMAGE_NAME \
    --image-scanning-configuration scanOnPush=true

# Enable Inspector V2 enhanced scanning
aws inspector2 enable \
    --account-ids $(aws sts get-caller-identity --query Account --output text) \
    --resource-types ECR

# Build and push image
docker build -t $FULL_IMAGE .
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin $REPOSITORY_URI
docker push $FULL_IMAGE

# Wait for scan to complete and get results
echo "Waiting for vulnerability scan to complete..."
sleep 30

# Get scan results
SCAN_RESULTS=$(aws ecr describe-image-scan-findings \
    --repository-name $IMAGE_NAME \
    --image-id imageTag=$IMAGE_TAG \
    --output json)

# Check for critical vulnerabilities
CRITICAL_COUNT=$(echo $SCAN_RESULTS | jq -r '.imageScanFindingsSummary.findingCounts.CRITICAL // 0')
HIGH_COUNT=$(echo $SCAN_RESULTS | jq -r '.imageScanFindingsSummary.findingCounts.HIGH // 0')

echo "Vulnerability Scan Results:"
echo "Critical: $CRITICAL_COUNT"
echo "High: $HIGH_COUNT"

# Fail build if critical vulnerabilities found
if [ "$CRITICAL_COUNT" -gt 0 ]; then
    echo "Build failed: Critical vulnerabilities found"
    echo $SCAN_RESULTS | jq -r '.imageScanFindings[] | select(.severity=="CRITICAL") | .description'
    exit 1
fi

# Generate SBOM (Software Bill of Materials)
syft $FULL_IMAGE -o json > sbom.json
grype $FULL_IMAGE -o json > vulnerability-report.json

echo "Container security scan completed successfully"

Distroless and Minimal Base Images

Implement secure container images using distroless and minimal base images:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Multi-stage build with security focus
FROM node:18-alpine AS builder

# Create non-root user
RUN addgroup -g 1001 -S appuser && \
    adduser -S -D -H -u 1001 -s /sbin/nologin -G appuser appuser

WORKDIR /app
COPY package*.json ./

# Install only production dependencies
RUN npm ci --only=production && \
    npm cache clean --force

COPY . .
RUN npm run build

# Production stage using distroless image
FROM gcr.io/distroless/nodejs18-debian11:nonroot

# Copy application and set ownership
COPY --from=builder --chown=nonroot:nonroot /app/dist /app
COPY --from=builder --chown=nonroot:nonroot /app/node_modules /app/node_modules

# Set working directory and user
WORKDIR /app
USER nonroot

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD ["node", "health-check.js"]

EXPOSE 8080
CMD ["index.js"]

Image Signing and Verification

Implement container image signing using AWS Signer and Notary v2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
# sign-container-image.sh

SIGNING_PROFILE_NAME="container-signing-profile"
IMAGE_URI="123456789.dkr.ecr.us-west-2.amazonaws.com/secure-app:latest"

# Create signing profile if it doesn't exist
aws signer describe-signing-job --profile-name $SIGNING_PROFILE_NAME 2>/dev/null || \
aws signer put-signing-profile \
    --profile-name $SIGNING_PROFILE_NAME \
    --platform-id "Notation-OCI-SHA384-ECDSA"

# Sign the container image
aws signer start-signing-job \
    --source '{
        "s3": {
            "bucketName": "container-signing-artifacts",
            "key": "unsigned/manifest.json",
            "version": "latest"
        }
    }' \
    --destination '{
        "s3": {
            "bucketName": "container-signing-artifacts",
            "prefix": "signed/"
        }
    }' \
    --profile-name $SIGNING_PROFILE_NAME

echo "Container image signed successfully"

Runtime Security and Threat Detection

AWS GuardDuty for Kubernetes Protection

Enable and configure GuardDuty for comprehensive Kubernetes threat detection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Enable GuardDuty
DETECTOR_ID=$(aws guardduty create-detector \
    --enable \
    --finding-publishing-frequency FIFTEEN_MINUTES \
    --query 'DetectorId' \
    --output text)

# Enable Kubernetes protection
aws guardduty update-detector \
    --detector-id $DETECTOR_ID \
    --data-sources '{
        "S3Logs": {
            "Enable": true
        },
        "Kubernetes": {
            "AuditLogs": {
                "Enable": true
            }
        },
        "MalwareProtection": {
            "ScanEc2InstanceWithFindings": {
                "EbsVolumes": true
            }
        }
    }'

# Create EventBridge rule for GuardDuty findings
aws events put-rule \
    --name guardduty-kubernetes-findings \
    --event-pattern '{
        "source": ["aws.guardduty"],
        "detail-type": ["GuardDuty Finding"],
        "detail": {
            "service": {
                "serviceName": ["EKS"]
            }
        }
    }'

Runtime Security with Falco

Deploy Falco for runtime security monitoring on EKS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# falco-deployment.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: falco
  namespace: falco-system
  labels:
    app: falco-exporter
    role: security
spec:
  selector:
    matchLabels:
      app: falco-exporter
      role: security
  template:
    metadata:
      labels:
        app: falco-exporter
        role: security
    spec:
      serviceAccount: falco
      hostNetwork: true
      hostPID: true
      hostIPC: true
      tolerations:
      - operator: Exists
        effect: NoSchedule
      containers:
      - name: falco
        image: falcosecurity/falco:0.36.2
        args:
          - /usr/bin/falco
          - --cri=/run/containerd/containerd.sock
          - --cri-timeout=1000ms
          - --k8s-api=https://kubernetes.default:443
          - --k8s-api-cert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
          - --k8s-api-token=/var/run/secrets/kubernetes.io/serviceaccount/token
        env:
        - name: K8S_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /host/var/run/docker.sock
          name: docker-socket
          readOnly: true
        - mountPath: /host/run/containerd/containerd.sock
          name: containerd-socket
          readOnly: true
        - mountPath: /host/dev
          name: dev-fs
          readOnly: true
        - mountPath: /host/proc
          name: proc-fs
          readOnly: true
        - mountPath: /host/boot
          name: boot-fs
          readOnly: true
        - mountPath: /host/lib/modules
          name: lib-modules
          readOnly: true
        - mountPath: /host/usr
          name: usr-fs
          readOnly: true
        - mountPath: /host/etc
          name: etc-fs
          readOnly: true
        - mountPath: /etc/falco
          name: falco-config
      volumes:
      - name: docker-socket
        hostPath:
          path: /var/run/docker.sock
      - name: containerd-socket
        hostPath:
          path: /run/containerd/containerd.sock
      - name: dev-fs
        hostPath:
          path: /dev
      - name: proc-fs
        hostPath:
          path: /proc
      - name: boot-fs
        hostPath:
          path: /boot
      - name: lib-modules
        hostPath:
          path: /lib/modules
      - name: usr-fs
        hostPath:
          path: /usr
      - name: etc-fs
        hostPath:
          path: /etc
      - name: falco-config
        configMap:
          name: falco-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: falco-config
  namespace: falco-system
data:
  falco.yaml: |
    rules_file:
      - /etc/falco/falco_rules.yaml
      - /etc/falco/falco_rules.local.yaml
      - /etc/falco/k8s_audit_rules.yaml
    
    json_output: true
    json_include_output_property: true
    
    http_output:
      enabled: true
      url: "http://falco-exporter:2801/"
    
    priority: debug
    buffered_outputs: false
    
    syscall_event_drops:
      actions:
        - log
        - alert
      rate: 0.03333
      max_burst: 1000
    
    metadata_download:
      max_mb: 100
      chunk_wait_us: 1000
      watch_freq_sec: 1
  
  k8s_audit_rules.yaml: |
    - rule: Anonymous Request Allowed
      desc: An anonymous request was allowed
      condition: ka and ka.user_name="system:anonymous"
      output: Anonymous request allowed (source=%ka.source.resource verb=%ka.verb.resource reason=%ka.response_reason)
      priority: WARNING
      tags: [k8s]
    
    - rule: Privileged Container Created
      desc: A privileged container was created
      condition: ka and ka.verb="create" and ka.target.subresource="" and ka.target.resource="pods" and ka.request_pod.privileged=true
      output: Privileged container created (user=%ka.user.name verb=%ka.verb.resource pod=%ka.request_pod.name namespace=%ka.namespace privileged=%ka.request_pod.privileged)
      priority: WARNING
      tags: [k8s]

Amazon Detective for Container Investigations

Enable Amazon Detective for advanced security investigation capabilities:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import boto3
import json
from datetime import datetime, timedelta

def setup_detective_for_containers():
    """Configure Amazon Detective for container security investigations"""
    detective = boto3.client('detective')
    guardduty = boto3.client('guardduty')
    
    # Create behavior graph
    try:
        response = detective.create_graph()
        graph_arn = response['GraphArn']
        print(f"Created Detective behavior graph: {graph_arn}")
    except detective.exceptions.ConflictException:
        # Graph already exists
        graphs = detective.list_graphs()['GraphList']
        graph_arn = graphs[0]['Arn'] if graphs else None
    
    # Enable data sources
    if graph_arn:
        detective.update_datasource_packages(
            GraphArn=graph_arn,
            DatasourcePackages=['DETECTIVE_CORE', 'EKS_AUDIT']
        )
    
    return graph_arn

def investigate_container_findings(graph_arn, finding_id):
    """Investigate container-related security findings"""
    detective = boto3.client('detective')
    
    # Get finding details from GuardDuty
    guardduty = boto3.client('guardduty')
    detectors = guardduty.list_detectors()['DetectorIds']
    
    for detector_id in detectors:
        finding = guardduty.get_findings(
            DetectorId=detector_id,
            FindingIds=[finding_id]
        )['Findings'][0]
        
        if 'EksClusterDetails' in finding.get('Service', {}):
            # Container-related finding
            cluster_arn = finding['Service']['EksClusterDetails']['Cluster']['Arn']
            
            # Query Detective for related entities
            end_time = datetime.utcnow()
            start_time = end_time - timedelta(hours=24)
            
            # Search for related container activities
            search_response = detective.search_graph(
                GraphArn=graph_arn,
                FilterCriteria={
                    'Severity': {'Value': 'HIGH'},
                    'CreatedTime': {
                        'StartInclusive': start_time,
                        'EndInclusive': end_time
                    }
                }
            )
            
            return {
                'finding': finding,
                'related_entities': search_response.get('Vertices', []),
                'investigation_paths': search_response.get('Edges', [])
            }

# Example usage
if __name__ == "__main__":
    graph_arn = setup_detective_for_containers()
    print(f"Detective configured for container investigations: {graph_arn}")

DevSecOps Pipeline Integration

Secure CI/CD Pipeline for Containers

Implement a comprehensive DevSecOps pipeline using AWS CodePipeline and security tools:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      docker: 20
    commands:
      # Install security scanning tools
      - pip install checkov bandit safety
      - wget -O /usr/local/bin/trivy.tar.gz https://github.com/aquasecurity/trivy/releases/latest/download/trivy_Linux-64bit.tar.gz
      - tar zxvf /usr/local/bin/trivy.tar.gz -C /usr/local/bin/
      - chmod +x /usr/local/bin/trivy
      
  pre_build:
    commands:
      # Static code analysis
      - echo "Running static security analysis..."
      - bandit -r . -f json -o bandit-report.json
      - safety check --json --output safety-report.json
      
      # Infrastructure as Code scanning
      - checkov -d . --framework kubernetes --output json --output-file checkov-report.json
      
      # Docker login
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      
  build:
    commands:
      # Build container image
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      
  post_build:
    commands:
      # Container vulnerability scanning
      - echo "Scanning container image for vulnerabilities..."
      - trivy image --format json --output trivy-report.json $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      
      # Check scan results
      - |
        CRITICAL_VULNS=$(cat trivy-report.json | jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length')
        if [ "$CRITICAL_VULNS" -gt 0 ]; then
          echo "Build failed: $CRITICAL_VULNS critical vulnerabilities found"
          exit 1
        fi
      
      # Push to ECR if security checks pass
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      
artifacts:
  files:
    - '**/*'
  name: container-security-artifacts
  base-directory: .

GitOps Security with ArgoCD

Implement secure GitOps deployment with policy enforcement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: secure-application
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: production
  source:
    repoURL: https://github.com/company/secure-app-config
    targetRevision: HEAD
    path: k8s/production
    helm:
      valueFiles:
      - values-production.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
    - CreateNamespace=true
    - PrunePropagationPolicy=foreground
    - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  # Security validations
  ignoreDifferences:
  - group: apps
    kind: Deployment
    jsonPointers:
    - /spec/replicas
  revisionHistoryLimit: 10
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
  namespace: argocd
spec:
  description: Production applications with security policies
  sourceRepos:
  - 'https://github.com/company/secure-app-config'
  destinations:
  - namespace: production
    server: https://kubernetes.default.svc
  clusterResourceWhitelist:
  - group: ''
    kind: Namespace
  - group: 'networking.k8s.io'
    kind: NetworkPolicy
  namespaceResourceWhitelist:
  - group: ''
    kind: Service
  - group: apps
    kind: Deployment
  - group: ''
    kind: ConfigMap
  - group: ''
    kind: Secret
  # Security policies
  syncWindows:
  - kind: allow
    schedule: "0 9 * * 1-5"  # Weekdays 9 AM
    duration: 8h
    applications:
    - secure-application
    manualSync: false

Policy as Code with Open Policy Agent (OPA) Gatekeeper

Implement security policies using OPA Gatekeeper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# security-policies.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8srequiredsecuritycontext
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredSecurityContext
      validation:
        openAPIV3Schema:
          type: object
          properties:
            runAsNonRoot:
              type: boolean
            readOnlyRootFilesystem:
              type: boolean
            allowPrivilegeEscalation:
              type: boolean
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredsecuritycontext

        violation[{"msg": msg}] {
            container := input.review.object.spec.containers[_]
            not container.securityContext.runAsNonRoot
            msg := "Container must run as non-root user"
        }

        violation[{"msg": msg}] {
            container := input.review.object.spec.containers[_]
            not container.securityContext.readOnlyRootFilesystem
            msg := "Container must use read-only root filesystem"
        }

        violation[{"msg": msg}] {
            container := input.review.object.spec.containers[_]
            container.securityContext.allowPrivilegeEscalation != false
            msg := "Container must not allow privilege escalation"
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredSecurityContext
metadata:
  name: security-context-constraint
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment", "DaemonSet", "StatefulSet"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
  parameters:
    runAsNonRoot: true
    readOnlyRootFilesystem: true
    allowPrivilegeEscalation: false

Compliance and Governance

AWS Config for Container Compliance

Implement automated compliance monitoring using AWS Config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import boto3
import json

def setup_container_compliance_rules():
    """Setup AWS Config rules for container compliance"""
    config = boto3.client('config')
    
    # EKS cluster logging rule
    eks_logging_rule = {
        'ConfigRuleName': 'eks-cluster-logging-enabled',
        'Source': {
            'Owner': 'AWS',
            'SourceIdentifier': 'EKS_CLUSTER_LOGGING_ENABLED'
        },
        'InputParameters': json.dumps({
            'desiredLogTypes': 'audit,authenticator,controllerManager'
        })
    }
    
    # ECR image scanning rule
    ecr_scanning_rule = {
        'ConfigRuleName': 'ecr-private-image-scanning-enabled',
        'Source': {
            'Owner': 'AWS',
            'SourceIdentifier': 'ECR_PRIVATE_IMAGE_SCANNING_ENABLED'
        }
    }
    
    # Container security group rule
    security_group_rule = {
        'ConfigRuleName': 'ec2-security-group-attached-to-eni',
        'Source': {
            'Owner': 'AWS',
            'SourceIdentifier': 'EC2_SECURITY_GROUP_ATTACHED_TO_ENI'
        }
    }
    
    rules = [eks_logging_rule, ecr_scanning_rule, security_group_rule]
    
    for rule in rules:
        try:
            config.put_config_rule(ConfigRule=rule)
            print(f"Created Config rule: {rule['ConfigRuleName']}")
        except Exception as e:
            print(f"Error creating rule {rule['ConfigRuleName']}: {str(e)}")

def create_compliance_dashboard():
    """Create CloudWatch dashboard for compliance monitoring"""
    cloudwatch = boto3.client('cloudwatch')
    
    dashboard_body = {
        "widgets": [
            {
                "type": "metric",
                "properties": {
                    "metrics": [
                        ["AWS/Config", "ComplianceByConfigRule", "ConfigRuleName", "eks-cluster-logging-enabled"],
                        [".", ".", ".", "ecr-private-image-scanning-enabled"],
                        [".", ".", ".", "ec2-security-group-attached-to-eni"]
                    ],
                    "period": 300,
                    "stat": "Average",
                    "region": "us-west-2",
                    "title": "Container Security Compliance"
                }
            },
            {
                "type": "log",
                "properties": {
                    "query": "SOURCE '/aws/eks/cluster-name/audit' | fields @timestamp, verb, objectRef.name, user.username\n| filter verb = \"create\" or verb = \"update\"\n| filter objectRef.resource = \"pods\"\n| stats count() by user.username",
                    "region": "us-west-2",
                    "title": "EKS Audit Log Analysis"
                }
            }
        ]
    }
    
    cloudwatch.put_dashboard(
        DashboardName='ContainerSecurityCompliance',
        DashboardBody=json.dumps(dashboard_body)
    )

if __name__ == "__main__":
    setup_container_compliance_rules()
    create_compliance_dashboard()
    print("Container compliance monitoring configured successfully")

Security Hub Integration

Integrate container security findings with AWS Security Hub:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import boto3
import json
from datetime import datetime

def send_custom_finding_to_security_hub(finding_data):
    """Send custom container security finding to AWS Security Hub"""
    securityhub = boto3.client('securityhub')
    
    finding = {
        'SchemaVersion': '2018-10-08',
        'Id': f"container-security/{finding_data['cluster_name']}/{finding_data['pod_name']}",
        'ProductArn': f"arn:aws:securityhub:us-west-2:123456789:product/123456789/container-security",
        'GeneratorId': 'container-security-scanner',
        'AwsAccountId': '123456789',
        'Types': ['Sensitive Data Identifications/Personally Identifiable Information'],
        'FirstObservedAt': datetime.utcnow().isoformat() + 'Z',
        'LastObservedAt': datetime.utcnow().isoformat() + 'Z',
        'CreatedAt': datetime.utcnow().isoformat() + 'Z',
        'UpdatedAt': datetime.utcnow().isoformat() + 'Z',
        'Severity': {
            'Label': finding_data['severity']
        },
        'Title': finding_data['title'],
        'Description': finding_data['description'],
        'Resources': [
            {
                'Type': 'AwsEksCluster',
                'Id': finding_data['cluster_arn'],
                'Region': 'us-west-2',
                'Details': {
                    'AwsEksCluster': {
                        'Arn': finding_data['cluster_arn'],
                        'Name': finding_data['cluster_name']
                    }
                }
            }
        ]
    }
    
    response = securityhub.batch_import_findings(Findings=[finding])
    return response

# Example usage
finding_data = {
    'cluster_name': 'production-cluster',
    'cluster_arn': 'arn:aws:eks:us-west-2:123456789:cluster/production-cluster',
    'pod_name': 'vulnerable-app-pod',
    'severity': 'HIGH',
    'title': 'Container Running with Privileged Access',
    'description': 'A container is running with privileged access, which could allow privilege escalation attacks.'
}

send_custom_finding_to_security_hub(finding_data)

Best Practices and Advanced Security Configurations

Pod Security Standards Implementation

Implement Pod Security Standards using built-in Kubernetes controls:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# pod-security-standards.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: secure-production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-application
  namespace: secure-production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: secure-application
  template:
    metadata:
      labels:
        app: secure-application
    spec:
      serviceAccountName: secure-app-service-account
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 3000
        fsGroup: 2000
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: app
        image: 123456789.dkr.ecr.us-west-2.amazonaws.com/secure-app:latest
        ports:
        - containerPort: 8080
        securityContext:
          allowPrivilegeEscalation: false
          runAsNonRoot: true
          runAsUser: 1000
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
        resources:
          limits:
            cpu: "1"
            memory: "1Gi"
          requests:
            cpu: "0.5"
            memory: "512Mi"
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: var-run
          mountPath: /var/run
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      - name: tmp
        emptyDir: {}
      - name: var-run
        emptyDir: {}

Secrets Management with AWS Secrets Manager

Integrate AWS Secrets Manager with EKS using the CSI driver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# secrets-csi-driver.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: app-secrets
  namespace: production
spec:
  provider: aws
  parameters:
    objects: |
      - objectName: "production/app/database-password"
        objectType: "secretsmanager"
        objectAlias: "db-password"
      - objectName: "production/app/api-key"
        objectType: "secretsmanager"
        objectAlias: "api-key"
      - objectName: "production/app/jwt-secret"
        objectType: "secretsmanager"
        objectAlias: "jwt-secret"
  secretObjects:
  - secretName: app-credentials
    type: Opaque
    data:
    - objectName: db-password
      key: database-password
    - objectName: api-key
      key: api-key
    - objectName: jwt-secret
      key: jwt-secret
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-with-secrets
  namespace: production
spec:
  template:
    spec:
      serviceAccountName: secrets-app-service-account
      containers:
      - name: app
        image: secure-app:latest
        volumeMounts:
        - name: secrets-store
          mountPath: "/mnt/secrets"
          readOnly: true
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-credentials
              key: database-password
      volumes:
      - name: secrets-store
        csi:
          driver: secrets-store.csi.k8s.io
          readOnly: true
          volumeAttributes:
            secretProviderClass: "app-secrets"

Implementation Roadmap

Phase 1: Foundation Security (Weeks 1-4)

  • EKS Cluster Hardening
    • Enable private endpoint access
    • Configure encryption at rest with AWS KMS
    • Enable comprehensive audit logging
    • Implement network policies
  • Identity and Access Management
    • Setup IRSA for service accounts
    • Implement fine-grained IAM policies
    • Configure RBAC policies
    • Enable MFA for cluster access
  • Image Security Foundation
    • Enable ECR vulnerability scanning
    • Implement distroless base images
    • Setup automated vulnerability scanning in CI/CD
    • Create image signing workflow

Phase 2: Detection and Monitoring (Weeks 5-8)

  • Threat Detection
    • Enable GuardDuty for Kubernetes protection
    • Deploy Falco for runtime security monitoring
    • Configure Amazon Detective
    • Setup Security Hub integration
  • Compliance Monitoring
    • Deploy AWS Config rules for container compliance
    • Implement OPA Gatekeeper policies
    • Create compliance dashboards
    • Setup automated compliance reporting
  • Logging and Auditing
    • Configure centralized logging with CloudWatch
    • Implement log analysis and alerting
    • Setup audit log retention policies
    • Create security investigation playbooks

Phase 3: Automation and DevSecOps (Weeks 9-12)

  • DevSecOps Pipeline Integration
    • Implement security scanning in CI/CD pipelines
    • Deploy GitOps with ArgoCD security policies
    • Automate security testing
    • Create security feedback loops
  • Policy as Code
    • Implement comprehensive OPA policies
    • Automate policy deployment and updates
    • Create policy compliance dashboards
    • Setup policy violation alerting
  • Incident Response Automation
    • Implement automated incident response workflows
    • Create security event correlation
    • Setup automated threat hunting
    • Deploy security orchestration tools

Phase 4: Advanced Security and Optimization (Weeks 13-16)

  • Zero Trust Architecture
    • Implement service mesh security (Istio)
    • Deploy mTLS for all inter-service communication
    • Implement dynamic security policies
    • Create microsegmentation strategies
  • Supply Chain Security
    • Implement SBOM generation and tracking
    • Deploy software composition analysis
    • Create dependency vulnerability monitoring
    • Implement provenance verification
  • Performance and Scale Optimization
    • Optimize security tool performance
    • Implement efficient log aggregation
    • Create scaling policies for security services
    • Deploy performance monitoring for security tools

Additional Resources

Official AWS Documentation

Security Tools and Frameworks

Industry Standards and Compliance

Community and Professional Resources

Conclusion

Implementing comprehensive container security on AWS requires a multi-layered approach that integrates security throughout the entire container lifecycle. From secure cluster configuration and image management to runtime protection and compliance automation, each component plays a crucial role in maintaining a robust security posture.

The strategies outlined in this guide provide a foundation for enterprise-grade container security that scales with your AWS infrastructure. By implementing automated vulnerability scanning, runtime threat detection, and DevSecOps pipeline integration, organizations can significantly reduce their container attack surface while maintaining development velocity.

Success in container security requires continuous monitoring, regular updates to security policies, and staying current with emerging threats and AWS security service enhancements. The implementation roadmap provides a structured approach to building comprehensive container security capabilities over a 16-week period.

For personalized guidance on implementing AWS EKS security and DevSecOps container strategies in your enterprise environment, connect with Jon Price on LinkedIn.

This post is licensed under CC BY 4.0 by the author.