EKS Security Best Practices
Versiones compatibles: Amazon EKS 1.31, 1.32, 1.33 Última actualización: February 22, 2026
Este documento cubre las mejores prácticas de seguridad para entornos de Amazon EKS. Aprende cómo operar clústeres de EKS de forma segura, desde la integración con IAM hasta la seguridad de red y la protección en tiempo de ejecución.
Table of Contents
- IRSA (IAM Roles for Service Accounts)
- EKS Pod Identity
- Security Groups for Pods
- VPC Endpoints
- Control Plane Logging
- GuardDuty EKS Protection
- Amazon Inspector
- CIS Kubernetes Benchmark
- Cluster Encryption
- Node Security
- Private Clusters
- Multi-tenancy Patterns
IRSA (IAM Roles for Service Accounts)
IRSA Overview
IRSA (IAM Roles for Service Accounts) asocia roles de IAM con Kubernetes ServiceAccounts, lo que permite que los Pods accedan de forma segura a los servicios de AWS.
┌─────────────────────────────────────────────────────────────────────────┐
│ IRSA Architecture │
│ │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ EKS Pod │ │ AWS IAM │ │
│ │ │ │ │ │
│ │ ServiceAcc │──────┐ │ ┌───────────────┐ │ │
│ │ (with IRSA) │ │ │ │ IAM Role │ │ │
│ └─────────────┘ │ │ │ │ │ │
│ │ │ │ │ Trust Policy: │ │ │
│ │ │ │ │ OIDC Provider │ │ │
│ ▼ │ │ └───────┬───────┘ │ │
│ ┌─────────────┐ │ ┌─────────────┐ │ │ │ │
│ │ Projected │ │ │ OIDC │ │ │ │ │
│ │ Token │──────┴──▶│ Provider │────┼──────────┘ │ │
│ └─────────────┘ └─────────────┘ │ │ │
│ │ ┌───────────────┐ │ │
│ │ │ AWS Services │ │ │
│ │ │ (S3, DynamoDB)│ │ │
│ │ └───────────────┘ │ │
└─────────────────────────────────────────────────────────────────────────┘IRSA Setup
# 1. Create OIDC Provider (once per cluster)
eksctl utils associate-iam-oidc-provider \
--cluster my-cluster \
--approve
# 2. Create IAM policy
cat <<EOF > s3-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
EOF
aws iam create-policy \
--policy-name S3ReadPolicy \
--policy-document file://s3-policy.json
# 3. Create IAM ServiceAccount
eksctl create iamserviceaccount \
--name s3-reader-sa \
--namespace production \
--cluster my-cluster \
--attach-policy-arn arn:aws:iam::123456789012:policy/S3ReadPolicy \
--approveUsing IRSA
# Verify ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-reader-sa
namespace: production
annotations:
# IRSA role ARN
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eksctl-my-cluster-s3-reader-sa
---
# Use ServiceAccount in Pod
apiVersion: v1
kind: Pod
metadata:
name: s3-reader
namespace: production
spec:
serviceAccountName: s3-reader-sa
containers:
- name: app
image: amazon/aws-cli
command: ["aws", "s3", "ls", "s3://my-bucket"]
# AWS SDK automatically uses IRSA tokenIRSA Trust Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:production:s3-reader-sa",
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
]
}IRSA Best Practices
# 1. Principle of least privilege
# Grant only minimum required permissions to each ServiceAccount
# 2. Separate ServiceAccounts per namespace
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dynamodb-reader
namespace: orders-service
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/orders-dynamodb-role
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-uploader
namespace: media-service
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/media-s3-roleEKS Pod Identity
Pod Identity Overview
EKS Pod Identity es un sucesor de IRSA que ofrece una configuración más simple y seguridad mejorada.
┌─────────────────────────────────────────────────────────────────────────┐
│ EKS Pod Identity Architecture │
│ │
│ ┌─────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ EKS Pod │────────▶│ Pod Identity │────────▶│ AWS IAM │ │
│ │ │ │ Agent │ │ Role │ │
│ │ ServiceAcc │ │ (DaemonSet) │ │ │ │
│ └─────────────┘ └─────────────────┘ └──────────────┘ │
│ │
│ Benefits: │
│ • No OIDC Provider setup required │
│ • Easy role reuse │
│ • Enhanced audit logging │
│ • Session tags support │
└─────────────────────────────────────────────────────────────────────────┘Pod Identity Setup
# 1. Install Pod Identity Agent addon
aws eks create-addon \
--cluster-name my-cluster \
--addon-name eks-pod-identity-agent \
--addon-version v1.3.0-eksbuild.1
# 2. Create IAM role (with Pod Identity trust policy)
cat <<EOF > trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "pods.eks.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession"
]
}
]
}
EOF
aws iam create-role \
--role-name my-pod-role \
--assume-role-policy-document file://trust-policy.json
# 3. Attach policy
aws iam attach-role-policy \
--role-name my-pod-role \
--policy-arn arn:aws:iam::123456789012:policy/S3ReadPolicy
# 4. Create Pod Identity Association
aws eks create-pod-identity-association \
--cluster-name my-cluster \
--namespace production \
--service-account my-app-sa \
--role-arn arn:aws:iam::123456789012:role/my-pod-roleUsing Pod Identity
# ServiceAccount (no annotation needed)
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: production
---
# Pod
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: production
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: myapp:latest
# AWS SDK automatically uses Pod IdentityIRSA vs Pod Identity Comparison
| Característica | IRSA | EKS Pod Identity |
|---|---|---|
| Complejidad de configuración | OIDC Provider requerido | Simple (llamada a la API) |
| Trust Policy | Se requieren condiciones OIDC | Principal de servicio simple |
| Reutilización de roles | Se necesita modificación por clúster | Reutilización entre clústeres |
| Audit Logging | CloudTrail (nivel de SA) | CloudTrail (nivel de Pod) |
| Session Tags | No compatible | Compatible |
| Recomendado | Entornos existentes | Entornos nuevos |
Security Groups for Pods
Overview
Security Groups for Pods aplica VPC Security Groups directamente a los Pods, lo que proporciona aislamiento a nivel de red.
Prerequisites
# Check VPC CNI version (v1.7.7+)
kubectl describe daemonset aws-node -n kube-system | grep Image
# Enable Security Groups for Pods
kubectl set env daemonset aws-node -n kube-system ENABLE_POD_ENI=true
# Add policy to node IAM role
aws iam attach-role-policy \
--role-name <node-role-name> \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceControllerSecurityGroupPolicy Configuration
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
name: database-sg-policy
namespace: production
spec:
# Target Pod selection
podSelector:
matchLabels:
app: database
# Security Groups to apply
securityGroups:
groupIds:
- sg-0123456789abcdef0 # Database SG
- sg-0987654321fedcba0 # Common monitoring SGSecurity Group Configuration with Terraform
# Security Group for database Pods
resource "aws_security_group" "database_pods" {
name_prefix = "database-pods-"
vpc_id = module.vpc.vpc_id
description = "Security group for database pods"
# PostgreSQL access (from app pods only)
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app_pods.id]
description = "PostgreSQL from app pods"
}
# Monitoring access
ingress {
from_port = 9187
to_port = 9187
protocol = "tcp"
security_groups = [aws_security_group.monitoring.id]
description = "Postgres exporter metrics"
}
# Replication traffic
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
self = true
description = "Replication between database pods"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "database-pods-sg"
}
}
# SecurityGroupPolicy
resource "kubernetes_manifest" "database_sg_policy" {
manifest = {
apiVersion = "vpcresources.k8s.aws/v1beta1"
kind = "SecurityGroupPolicy"
metadata = {
name = "database-sg-policy"
namespace = "production"
}
spec = {
podSelector = {
matchLabels = {
app = "database"
}
}
securityGroups = {
groupIds = [aws_security_group.database_pods.id]
}
}
}
}VPC Endpoints
VPC Endpoints for Private EKS
┌─────────────────────────────────────────────────────────────────────────┐
│ EKS VPC Endpoint Configuration │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ VPC │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Private │ │ Private │ │ Private │ │ │
│ │ │ Subnet A │ │ Subnet B │ │ Subnet C │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ │
│ │ │ │ Node │ │ │ │ Node │ │ │ │ Node │ │ │ │
│ │ │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ └──────────────────┼──────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐│ │
│ │ │ VPC Endpoints ││ │
│ │ │ • com.amazonaws.region.eks ││ │
│ │ │ • com.amazonaws.region.ecr.api ││ │
│ │ │ • com.amazonaws.region.ecr.dkr ││ │
│ │ │ • com.amazonaws.region.s3 (Gateway) ││ │
│ │ │ • com.amazonaws.region.sts ││ │
│ │ │ • com.amazonaws.region.logs ││ │
│ │ │ • com.amazonaws.region.elasticloadbalancing ││ │
│ │ └─────────────────────────────────────────────────────────────┘│ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘Terraform VPC Endpoint Setup
# Interface endpoints
locals {
interface_endpoints = [
"com.amazonaws.${var.region}.eks",
"com.amazonaws.${var.region}.ecr.api",
"com.amazonaws.${var.region}.ecr.dkr",
"com.amazonaws.${var.region}.sts",
"com.amazonaws.${var.region}.logs",
"com.amazonaws.${var.region}.elasticloadbalancing",
"com.amazonaws.${var.region}.autoscaling",
"com.amazonaws.${var.region}.ssm",
"com.amazonaws.${var.region}.ssmmessages",
"com.amazonaws.${var.region}.ec2messages",
]
}
resource "aws_vpc_endpoint" "interface_endpoints" {
for_each = toset(local.interface_endpoints)
vpc_id = module.vpc.vpc_id
service_name = each.value
vpc_endpoint_type = "Interface"
subnet_ids = module.vpc.private_subnets
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = {
Name = "vpce-${split(".", each.value)[4]}"
}
}
# Gateway endpoints (S3, DynamoDB)
resource "aws_vpc_endpoint" "s3" {
vpc_id = module.vpc.vpc_id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = module.vpc.private_route_table_ids
tags = {
Name = "vpce-s3"
}
}
# Security Group for VPC endpoints
resource "aws_security_group" "vpc_endpoints" {
name_prefix = "vpc-endpoints-"
vpc_id = module.vpc.vpc_id
description = "Security group for VPC endpoints"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr_block]
description = "HTTPS from VPC"
}
tags = {
Name = "vpc-endpoints-sg"
}
}Control Plane Logging
EKS Control Plane Log Types
| Tipo de log | Descripción | Caso de uso |
|---|---|---|
| api | Logs del servidor Kubernetes API | Seguimiento de llamadas API, solución de problemas |
| audit | Logs de auditoría de Kubernetes | Auditoría de seguridad, cumplimiento |
| authenticator | Logs del autenticador AWS IAM | Depuración de autenticación |
| controllerManager | Logs del controller manager | Seguimiento del comportamiento del controller |
| scheduler | Logs del scheduler | Solución de problemas de programación de Pods |
Enabling Logging
# Enable logging with AWS CLI
aws eks update-cluster-config \
--name my-cluster \
--logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'# Terraform logging configuration
resource "aws_eks_cluster" "main" {
name = "my-cluster"
role_arn = aws_iam_role.cluster.arn
version = "1.31"
vpc_config {
subnet_ids = module.vpc.private_subnets
endpoint_private_access = true
endpoint_public_access = true
}
enabled_cluster_log_types = [
"api",
"audit",
"authenticator",
"controllerManager",
"scheduler"
]
}CloudWatch Logs Insights Queries
-- Find failed authentication attempts
fields @timestamp, @message
| filter @logStream like /authenticator/
| filter @message like /error/
| sort @timestamp desc
| limit 100
-- Track API calls for specific user
fields @timestamp, user.username, verb, requestURI, responseStatus.code
| filter @logStream like /audit/
| filter user.username = "admin"
| sort @timestamp desc
| limit 50
-- Permission denied requests
fields @timestamp, user.username, verb, requestURI, responseStatus.code
| filter @logStream like /audit/
| filter responseStatus.code = 403
| sort @timestamp desc
| limit 100
-- Secret access audit
fields @timestamp, user.username, verb, requestURI
| filter @logStream like /audit/
| filter requestURI like /secrets/
| sort @timestamp desc
| limit 100GuardDuty EKS Protection
GuardDuty EKS Protection Overview
Amazon GuardDuty EKS Protection detecta actividad maliciosa y comportamiento anómalo en clústeres de EKS.
┌─────────────────────────────────────────────────────────────────────────┐
│ GuardDuty EKS Protection │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Detection Areas │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ EKS Audit │ │ Runtime │ │ VPC Flow │ │ │
│ │ │ Logs │ │ Monitoring │ │ Logs │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┼────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ GuardDuty ML │ │ │
│ │ │ Threat Detection │ │ │
│ │ └───────────┬───────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ Findings │ │ │
│ │ │ (Security Events) │ │ │
│ │ └───────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘Enabling GuardDuty
# Enable GuardDuty
aws guardduty create-detector --enable
# Enable EKS Protection
DETECTOR_ID=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws guardduty update-detector \
--detector-id $DETECTOR_ID \
--features '[
{"Name": "EKS_AUDIT_LOGS", "Status": "ENABLED"},
{"Name": "EKS_RUNTIME_MONITORING", "Status": "ENABLED",
"AdditionalConfiguration": [
{"Name": "EKS_ADDON_MANAGEMENT", "Status": "ENABLED"}
]
}
]'GuardDuty EKS Finding Types
| Tipo de finding | Descripción | Gravedad |
|---|---|---|
Kubernetes:MaliciousIPCaller.Custom | Llamada API desde una IP maliciosa conocida | Alta |
Kubernetes:SuccessfulAnonymousAccess | Llamada API exitosa por usuario anónimo | Media |
Kubernetes:PrivilegedContainer | Creación de contenedor privilegiado | Media |
Kubernetes:AnomalousBehavior.PermissionChecked | Comprobación de permisos anómala | Baja |
Runtime:Cryptocurrency.CoinMiner | Actividad de minería de criptomonedas | Alta |
Runtime:ReverseShell | Conexión de reverse shell | Crítica |
Runtime:Execution.Suspicious | Ejecución de proceso sospechoso | Media |
Automated Finding Response
# EventBridge Rule
Resources:
GuardDutyEKSRule:
Type: AWS::Events::Rule
Properties:
Name: guardduty-eks-findings
EventPattern:
source:
- aws.guardduty
detail-type:
- GuardDuty Finding
detail:
type:
- prefix: Kubernetes
- prefix: Runtime
Targets:
- Id: SNSTarget
Arn: !Ref GuardDutyAlertTopic
- Id: LambdaTarget
Arn: !GetAtt GuardDutyResponseFunction.Arn
GuardDutyResponseFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: guardduty-eks-response
Runtime: python3.11
Handler: index.handler
Code:
ZipFile: |
import boto3
import json
def handler(event, context):
finding = event['detail']
severity = finding['severity']
if severity >= 7: # High severity
# Auto-response: Pod isolation
eks = boto3.client('eks')
# Additional response logic...
return {'statusCode': 200}Amazon Inspector
Inspector Container Image Scanning
# Enable ECR enhanced scanning
aws ecr put-registry-scanning-configuration \
--scan-type ENHANCED \
--rules '[
{
"repositoryFilters": [{"filter": "*", "filterType": "WILDCARD"}],
"scanFrequency": "CONTINUOUS_SCAN"
}
]'
# Get scan results
aws ecr describe-image-scan-findings \
--repository-name my-app \
--image-id imageTag=latestInspector and CI/CD Integration
# GitHub Actions example
name: Build and Scan
on:
push:
branches: [main]
jobs:
build-scan-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image
run: |
docker build -t $ECR_REGISTRY/my-app:${{ github.sha }} .
docker push $ECR_REGISTRY/my-app:${{ github.sha }}
- name: Wait for Inspector scan
run: |
sleep 60 # Wait for Inspector to complete scan
- name: Check scan results
run: |
FINDINGS=$(aws ecr describe-image-scan-findings \
--repository-name my-app \
--image-id imageTag=${{ github.sha }} \
--query 'imageScanFindings.findingSeverityCounts')
CRITICAL=$(echo $FINDINGS | jq '.CRITICAL // 0')
HIGH=$(echo $FINDINGS | jq '.HIGH // 0')
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 5 ]; then
echo "Security scan failed: CRITICAL=$CRITICAL, HIGH=$HIGH"
exit 1
fiCIS Kubernetes Benchmark
Running kube-bench
# Run kube-bench as a Job
kubectl apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: kube-bench
namespace: kube-system
spec:
template:
spec:
hostPID: true
containers:
- name: kube-bench
image: aquasec/kube-bench:latest
command: ["kube-bench", "--benchmark", "eks-1.4.0"]
volumeMounts:
- name: var-lib-kubelet
mountPath: /var/lib/kubelet
readOnly: true
- name: etc-systemd
mountPath: /etc/systemd
readOnly: true
- name: etc-kubernetes
mountPath: /etc/kubernetes
readOnly: true
restartPolicy: Never
volumes:
- name: var-lib-kubelet
hostPath:
path: /var/lib/kubelet
- name: etc-systemd
hostPath:
path: /etc/systemd
- name: etc-kubernetes
hostPath:
path: /etc/kubernetes
EOF
# Check results
kubectl logs job/kube-bench -n kube-systemCIS Benchmark Key Sections
| Sección | Descripción | Relevancia en EKS |
|---|---|---|
| 1. Control Plane | Servidor API, etcd, etc. | Gestionado por AWS |
| 2. etcd | Seguridad de etcd | Gestionado por AWS |
| 3. Control Plane Configuration | Controllers, scheduler | Gestionado por AWS |
| 4. Worker Nodes | Configuración de Node | Responsabilidad del usuario |
| 5. Policies | RBAC, PSP/PSS, red | Responsabilidad del usuario |
Automated Compliance Checks
# CronJob for regular checks
apiVersion: batch/v1
kind: CronJob
metadata:
name: cis-benchmark-scan
namespace: security
spec:
schedule: "0 2 * * 0" # Every Sunday at 02:00
jobTemplate:
spec:
template:
spec:
serviceAccountName: cis-scanner
containers:
- name: kube-bench
image: aquasec/kube-bench:latest
command:
- /bin/sh
- -c
- |
kube-bench --benchmark eks-1.4.0 --json > /tmp/results.json
# Upload results to S3
aws s3 cp /tmp/results.json s3://security-reports/cis-benchmark/$(date +%Y-%m-%d).json
restartPolicy: OnFailureCluster Encryption
EKS Secrets Encryption (KMS)
# Create KMS key
aws kms create-key \
--description "EKS Secrets Encryption Key" \
--key-usage ENCRYPT_DECRYPT
# Create cluster with encryption
aws eks create-cluster \
--name my-cluster \
--role-arn arn:aws:iam::123456789012:role/EKSClusterRole \
--resources-vpc-config subnetIds=subnet-xxx,subnet-yyy \
--encryption-config '[{
"resources": ["secrets"],
"provider": {
"keyArn": "arn:aws:kms:us-east-1:123456789012:key/xxx-xxx"
}
}]'# Terraform configuration
resource "aws_kms_key" "eks_secrets" {
description = "EKS Secrets Encryption Key"
deletion_window_in_days = 7
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Enable IAM policies"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
}
]
})
}
resource "aws_eks_cluster" "main" {
name = "my-cluster"
role_arn = aws_iam_role.cluster.arn
encryption_config {
resources = ["secrets"]
provider {
key_arn = aws_kms_key.eks_secrets.arn
}
}
# ... other configuration
}Node Security
Bottlerocket OS
# Bottlerocket node group
resource "aws_eks_node_group" "bottlerocket" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "bottlerocket-nodes"
node_role_arn = aws_iam_role.node.arn
subnet_ids = module.vpc.private_subnets
ami_type = "BOTTLEROCKET_x86_64"
instance_types = ["m5.large"]
scaling_config {
desired_size = 3
max_size = 10
min_size = 1
}
# Bottlerocket configuration
launch_template {
id = aws_launch_template.bottlerocket.id
version = aws_launch_template.bottlerocket.latest_version
}
}
resource "aws_launch_template" "bottlerocket" {
name_prefix = "bottlerocket-"
user_data = base64encode(<<-EOF
[settings.kubernetes]
cluster-name = "${aws_eks_cluster.main.name}"
api-server = "${aws_eks_cluster.main.endpoint}"
cluster-certificate = "${aws_eks_cluster.main.certificate_authority[0].data}"
[settings.kubernetes.node-labels]
"node.kubernetes.io/os" = "bottlerocket"
[settings.kernel.sysctl]
"net.core.rmem_max" = "16777216"
"net.core.wmem_max" = "16777216"
EOF
)
}Node Security Hardening
# Pod scheduling based on node labels
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
nodeSelector:
node.kubernetes.io/os: bottlerocket
tolerations:
- key: "security"
operator: "Equal"
value: "high"
effect: "NoSchedule"
containers:
- name: app
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: truePrivate Clusters
Fully Private EKS Configuration
resource "aws_eks_cluster" "private" {
name = "private-cluster"
role_arn = aws_iam_role.cluster.arn
version = "1.31"
vpc_config {
subnet_ids = module.vpc.private_subnets
endpoint_private_access = true
endpoint_public_access = false # Disable public endpoint
security_group_ids = [aws_security_group.cluster.id]
}
# Control plane logging
enabled_cluster_log_types = [
"api", "audit", "authenticator", "controllerManager", "scheduler"
]
# Secrets encryption
encryption_config {
resources = ["secrets"]
provider {
key_arn = aws_kms_key.eks.arn
}
}
depends_on = [
aws_iam_role_policy_attachment.cluster_policy,
aws_cloudwatch_log_group.cluster,
]
}Bastion or VPN Access
# Client VPN endpoint
resource "aws_ec2_client_vpn_endpoint" "eks_access" {
description = "EKS Access VPN"
server_certificate_arn = aws_acm_certificate.vpn.arn
client_cidr_block = "10.100.0.0/16"
authentication_options {
type = "certificate-authentication"
root_certificate_chain_arn = aws_acm_certificate.vpn_root.arn
}
connection_log_options {
enabled = true
cloudwatch_log_group = aws_cloudwatch_log_group.vpn.name
}
vpc_id = module.vpc.vpc_id
security_group_ids = [aws_security_group.vpn.id]
split_tunnel = true
}
resource "aws_ec2_client_vpn_network_association" "eks_access" {
count = length(module.vpc.private_subnets)
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.eks_access.id
subnet_id = module.vpc.private_subnets[count.index]
}Multi-tenancy Patterns
Namespace-based Multi-tenancy
---
# Tenant namespace
apiVersion: v1
kind: Namespace
metadata:
name: tenant-a
labels:
tenant: tenant-a
environment: production
pod-security.kubernetes.io/enforce: restricted
---
# ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
persistentvolumeclaims: "10"
services.loadbalancers: "2"
---
# LimitRange
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-a-limits
namespace: tenant-a
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "2"
memory: 4Gi
min:
cpu: 50m
memory: 64Mi
type: Container
---
# NetworkPolicy (tenant isolation)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: tenant-isolation
namespace: tenant-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: tenant-a
egress:
- to:
- namespaceSelector:
matchLabels:
tenant: tenant-a
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- port: 53
protocol: UDPRBAC Multi-tenancy
---
# Tenant admin role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tenant-admin
namespace: tenant-a
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets"]
verbs: ["*"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["*"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["*"]
---
# Tenant admin binding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-a-admins
namespace: tenant-a
subjects:
- kind: Group
name: tenant-a-admins
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-admin
apiGroup: rbac.authorization.k8s.ioSummary
Mejores prácticas clave de seguridad en EKS:
- Integración con IAM: Accede a servicios de AWS con IRSA o Pod Identity
- Seguridad de red: Security Groups for Pods, VPC endpoints
- Logging y monitoreo: Logs del control plane, GuardDuty
- Seguridad de imágenes: Amazon Inspector, escaneo de ECR
- Cumplimiento: CIS Benchmark, kube-bench
- Cifrado: Cifrado de Secrets con KMS
- Seguridad de Node: Bottlerocket OS, least privilege
- Multi-tenancy: Aislamiento por Namespace, RBAC, ResourceQuota