Service Account en EKS

Hello World 🙂
En el artículo de hoy hablaremos sobre uno de los recursos más importantes cuando pensamos en seguridad dentro de Amazon EKS: las ServiceAccount.
ServiceAccount en EKS: cómo dar acceso seguro a servicios de AWS desde un Pod
Cuando desplegamos aplicaciones en Kubernetes, normalmente estas aplicaciones se ejecutan dentro de Pods. En muchos casos, esos Pods necesitan acceder a servicios de AWS como Amazon S3, DynamoDB, KMS, Secrets Manager, SQS, SNS, entre otros.
La pregunta importante es:
¿Cómo le damos permisos de AWS a un Pod sin guardar credenciales dentro del contenedor y sin darle permisos excesivos al nodo?
Aquí es donde entran en juego las ServiceAccount en EKS junto con IAM Roles for Service Accounts, conocido comúnmente como IRSA.
¿Qué es una ServiceAccount?
Una ServiceAccount es una cuenta utilizada por Kubernetes para asignar una identidad a los procesos que se ejecutan dentro de un Pod.
Dicho de una forma simple:
Una ServiceAccount es la identidad que usa un Pod dentro de Kubernetes.
Por defecto, si no definimos una ServiceAccount específica para un Pod, Kubernetes utiliza la ServiceAccount llamada default dentro del namespace correspondiente.
Sin embargo, desde el punto de vista de seguridad, lo ideal es crear una ServiceAccount específica para cada aplicación o componente, y asociarle únicamente los permisos que realmente necesita.
En Amazon EKS, esta idea se vuelve todavía más importante porque podemos conectar una ServiceAccount de Kubernetes con un rol de IAM.
ServiceAccount en EKS e integración con IAM
Cuando trabajamos con Amazon EKS, una ServiceAccount puede integrarse con AWS IAM mediante IAM Roles for Service Accounts, conocido como IRSA.
IRSA permite asociar un rol IAM a una ServiceAccount de Kubernetes. De esta forma, los Pods que usen esa ServiceAccount pueden acceder a servicios de AWS utilizando los permisos definidos en ese rol IAM, sin necesidad de guardar credenciales estáticas dentro del contenedor ni depender directamente del rol IAM del nodo EC2.
La relación queda así:
Pod → ServiceAccount → IRSA → IAM Role → IAM Policy → Servicio de AWS
Por ejemplo:
Pod de la aplicación
|
v
ServiceAccount s3-reader-sa
|
v
IAM Role eks-irsa-s3-read-role
|
v
IAM Policy con permisos sobre S3
|
v
Bucket S3
Este modelo nos permite aplicar el principio de mínimo privilegio, porque cada aplicación puede tener su propia ServiceAccount y su propio rol IAM con permisos limitados.
¿Por qué no usar el rol IAM del nodo?
Una confusión común cuando empezamos a trabajar con EKS es pensar que basta con agregar permisos al rol IAM de los worker nodes.
Aunque técnicamente esto puede funcionar, no es la mejor práctica.
Cuando adjuntamos permisos al rol IAM del nodo, esos permisos quedan asociados al nodo EC2 donde corren los Pods. El problema es que en un mismo nodo pueden ejecutarse muchos Pods de diferentes aplicaciones.
Por ejemplo, si agregamos permisos de S3 al rol del nodo, podríamos terminar dando acceso a S3 a más aplicaciones de las necesarias.
Con IRSA, en cambio, el permiso no se asigna al nodo, sino a la ServiceAccount que usa el Pod.
Esto permite algo mucho más seguro:
Pod A → ServiceAccount A → IAM Role A → Acceso solo a S3
Pod B → ServiceAccount B → IAM Role B → Acceso solo a KMS
Pod C → ServiceAccount C → IAM Role C → Acceso solo a DynamoDB
AWS también indica que los contenedores no deben considerarse una frontera de seguridad absoluta y que, dependiendo de la configuración, los Pods podrían tener acceso a credenciales del nodo. Por eso, usar IRSA ayuda a separar mejor los permisos de cada workload.
Ventajas de usar ServiceAccount con IRSA en EKS
Usar ServiceAccount junto con IRSA ofrece varias ventajas importantes.
1. Permisos más granulares
Podemos asignar permisos específicos por aplicación.
Por ejemplo:
Aplicación A → Acceso solo a un bucket S3
Aplicación B → Acceso solo a una tabla DynamoDB
Aplicación C → Acceso solo a una clave KMS
Esto evita dar permisos generales a todos los Pods del clúster.
2. Mejor seguridad
Cada Pod obtiene únicamente los permisos que necesita.
Si una aplicación se ve comprometida, el impacto se reduce porque esa aplicación no tiene acceso a todos los recursos de AWS, sino solo a los recursos definidos en su política IAM.
3. No necesitamos credenciales estáticas
No hace falta guardar access keys dentro de Secrets, variables de entorno o imágenes Docker.
El Pod obtiene credenciales temporales utilizando la integración entre la ServiceAccount, el proveedor OIDC del clúster y AWS STS. AWS documenta que los SDK compatibles pueden usar el token OIDC de la ServiceAccount para llamar a AssumeRoleWithWebIdentity y obtener credenciales temporales.
4. Separación entre permisos del nodo y permisos de la aplicación
El rol IAM del nodo debe tener los permisos necesarios para que el nodo funcione correctamente dentro del clúster.
Los permisos de las aplicaciones deberían gestionarse aparte, usando:
ServiceAccount + IRSA + IAM Role
5. Mejor trazabilidad
Al usar roles IAM específicos por aplicación, es más fácil identificar qué workload está accediendo a qué recurso de AWS.
Esto ayuda en troubleshooting, auditorías, revisiones de seguridad y análisis de eventos en CloudTrail.
Comparación rápida
| Opción | Dónde se asignan los permisos | Granularidad | Recomendación |
|---|---|---|---|
| Permisos en el rol del nodo | Worker node EC2 | Baja | Evitar para permisos de aplicaciones |
| Credenciales estáticas en el Pod | Secret, variable o imagen | Media | No recomendado |
| ServiceAccount + IRSA | Pod / aplicación | Alta | Recomendado en EKS |
Ejemplo práctico: ServiceAccount con acceso a S3 usando IRSA en EKS
Ahora vamos a crear un ejemplo completo.
La idea será permitir que un Pod dentro de EKS pueda listar y leer objetos de un bucket S3 usando una ServiceAccount asociada a un IAM Role mediante IRSA.
La relación final será:
Pod → ServiceAccount → IRSA → IAM Role → IAM Policy → S3
Vamos a crear los siguientes recursos:
1. Un bucket S3 de prueba.
2. Una política IAM con permisos mínimos sobre ese bucket.
3. Un IAM Role que podrá ser asumido por una ServiceAccount específica.
4. Una ServiceAccount en Kubernetes anotada con el IAM Role.
5. Un Pod que usará esa ServiceAccount.
6. Una prueba final ejecutando comandos de AWS CLI desde el Pod.
1. Definir variables del ejemplo
Primero definimos algunas variables para reutilizarlas durante todo el laboratorio:
export AWS_REGION="eu-west-2"
export CLUSTER_NAME="demo-eks"
export NAMESPACE="demo-irsa"
export SERVICE_ACCOUNT_NAME="s3-reader-sa"
export IAM_POLICY_NAME="eks-irsa-s3-read-policy"
export IAM_ROLE_NAME="eks-irsa-s3-read-role"
export S3_BUCKET_NAME="demo-irsa-s3-bucket-123456789"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
Ajusta los valores de AWS_REGION, CLUSTER_NAME y S3_BUCKET_NAME según tu ambiente.
El nombre del bucket S3 debe ser único globalmente.
2. Crear el namespace
Creamos el namespace donde desplegaremos la ServiceAccount y el Pod:
kubectl create namespace $NAMESPACE
Validamos:
kubectl get namespace $NAMESPACE
3. Crear un bucket S3 de prueba
Creamos un bucket S3 simple para el ejemplo:
aws s3api create-bucket \
--bucket $S3_BUCKET_NAME \
--region $AWS_REGION \
--create-bucket-configuration LocationConstraint=$AWS_REGION
Subimos un archivo de prueba:
echo "Prueba de acceso desde EKS usando IRSA" > demo.txt
aws s3 cp demo.txt s3://$S3_BUCKET_NAME/demo.txt
Validamos que el objeto exista:
aws s3 ls s3://$S3_BUCKET_NAME/
4. Validar el proveedor OIDC del clúster EKS
Para que IRSA funcione, el clúster EKS debe tener un IAM OIDC Provider asociado. Este paso normalmente se hace una vez por clúster. AWS documenta que, para usar IAM Roles for Service Accounts, debe existir un proveedor IAM OIDC para la URL del issuer OIDC del clúster.
Obtenemos el ID del OIDC issuer del clúster:
oidc_id=$(aws eks describe-cluster \
--name $CLUSTER_NAME \
--region $AWS_REGION \
--query "cluster.identity.oidc.issuer" \
--output text | cut -d '/' -f 5)
echo $oidc_id
Validamos si ya existe un IAM OIDC Provider asociado:
aws iam list-open-id-connect-providers | grep $oidc_id
Si no devuelve ningún resultado, podemos asociarlo usando eksctl:
eksctl utils associate-iam-oidc-provider \
--cluster $CLUSTER_NAME \
--region $AWS_REGION \
--approve
5. Crear la política IAM con acceso limitado a S3
Ahora creamos una política IAM con permisos mínimos.
En este caso solo permitiremos:
s3:ListBucket
s3:GetObject
Creamos el archivo s3-read-policy.json:
cat > s3-read-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListSpecificBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::$S3_BUCKET_NAME"
},
{
"Sid": "AllowReadObjectsFromSpecificBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::$S3_BUCKET_NAME/*"
}
]
}
EOF
Creamos la política IAM:
aws iam create-policy \
--policy-name $IAM_POLICY_NAME \
--policy-document file://s3-read-policy.json
Guardamos el ARN de la política:
export IAM_POLICY_ARN="arn:aws:iam::$AWS_ACCOUNT_ID:policy/$IAM_POLICY_NAME"
echo $IAM_POLICY_ARN
6. Crear el trust policy para IRSA
Este es uno de los puntos más importantes del ejemplo.
El IAM Role no podrá ser asumido por cualquier Pod del clúster. Solo podrá ser asumido por la ServiceAccount específica que vamos a crear:
Namespace: demo-irsa
ServiceAccount: s3-reader-sa
Obtenemos el OIDC Provider completo:
export OIDC_PROVIDER=$(aws eks describe-cluster \
--name $CLUSTER_NAME \
--region $AWS_REGION \
--query "cluster.identity.oidc.issuer" \
--output text | sed -e "s/^https:\/\///")
echo $OIDC_PROVIDER
Creamos el archivo trust-policy.json:
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::$AWS_ACCOUNT_ID:oidc-provider/$OIDC_PROVIDER"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"$OIDC_PROVIDER:aud": "sts.amazonaws.com",
"$OIDC_PROVIDER:sub": "system:serviceaccount:$NAMESPACE:$SERVICE_ACCOUNT_NAME"
}
}
}
]
}
EOF
La parte más importante es esta:
"$OIDC_PROVIDER:sub": "system:serviceaccount:$NAMESPACE:$SERVICE_ACCOUNT_NAME"
Esto limita el uso del rol IAM únicamente a esa ServiceAccount dentro de ese namespace.
AWS recomienda que la trust policy de IRSA sea lo más explícita posible, incluyendo el nombre de la ServiceAccount para evitar que el rol pueda ser asumido por identidades no deseadas.
7. Crear el IAM Role
Creamos el rol IAM usando el trust policy anterior:
aws iam create-role \
--role-name $IAM_ROLE_NAME \
--assume-role-policy-document file://trust-policy.json
Adjuntamos la política de S3 al rol:
aws iam attach-role-policy \
--role-name $IAM_ROLE_NAME \
--policy-arn $IAM_POLICY_ARN
Guardamos el ARN del rol:
export IAM_ROLE_ARN="arn:aws:iam::$AWS_ACCOUNT_ID:role/$IAM_ROLE_NAME"
echo $IAM_ROLE_ARN
8. Crear la ServiceAccount en Kubernetes
Ahora creamos la ServiceAccount en Kubernetes y la anotamos con el ARN del IAM Role.
AWS documenta que una ServiceAccount puede asociarse a un IAM Role mediante la anotación eks.amazonaws.com/role-arn. Los Pods configurados para usar esa ServiceAccount podrán acceder a los servicios de AWS permitidos por el rol.
Creamos el archivo service-account.yaml:
cat > service-account.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: $SERVICE_ACCOUNT_NAME
namespace: $NAMESPACE
annotations:
eks.amazonaws.com/role-arn: $IAM_ROLE_ARN
EOF
Aplicamos el manifiesto:
kubectl apply -f service-account.yaml
Validamos la ServiceAccount:
kubectl get serviceaccount $SERVICE_ACCOUNT_NAME -n $NAMESPACE -o yaml
Deberíamos ver algo similar a esto:
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eks-irsa-s3-read-role
name: s3-reader-sa
namespace: demo-irsa
9. Crear un Pod usando la ServiceAccount
Ahora creamos un Pod que use la ServiceAccount s3-reader-sa.
Creamos el archivo pod-s3-test.yaml:
cat > pod-s3-test.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: aws-cli-irsa-test
namespace: $NAMESPACE
spec:
serviceAccountName: $SERVICE_ACCOUNT_NAME
restartPolicy: Never
containers:
- name: aws-cli
image: public.ecr.aws/aws-cli/aws-cli:latest
command:
- /bin/sh
- -c
- |
echo "Validando identidad AWS usada por el Pod..."
aws sts get-caller-identity
echo "Listando objetos del bucket permitido..."
aws s3 ls s3://$S3_BUCKET_NAME/
echo "Intentando leer el objeto demo.txt..."
aws s3 cp s3://$S3_BUCKET_NAME/demo.txt -
echo "Prueba finalizada correctamente."
EOF
Aplicamos el Pod:
kubectl apply -f pod-s3-test.yaml
Validamos el estado:
kubectl get pod aws-cli-irsa-test -n $NAMESPACE
Revisamos los logs:
kubectl logs aws-cli-irsa-test -n $NAMESPACE
La salida debería mostrar algo parecido a esto:
Validando identidad AWS usada por el Pod...
{
"UserId": "AROA...",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/eks-irsa-s3-read-role/..."
}
Listando objetos del bucket permitido...
2026-06-07 demo.txt
Intentando leer el objeto demo.txt...
Prueba de acceso desde EKS usando IRSA
Prueba finalizada correctamente.
Lo importante es validar que el ARN pertenece al rol:
arn:aws:sts::ACCOUNT_ID:assumed-role/eks-irsa-s3-read-role/...
Eso confirma que el Pod está usando el IAM Role asociado a la ServiceAccount y no el rol del worker node.
10. Validar las variables inyectadas por IRSA
Cuando el Pod usa una ServiceAccount anotada con un IAM Role, EKS configura el entorno necesario para que el SDK o AWS CLI puedan obtener credenciales temporales mediante web identity.
Podemos validar las variables dentro del Pod con:
kubectl describe pod aws-cli-irsa-test -n $NAMESPACE | grep AWS_ROLE_ARN
Y también:
kubectl describe pod aws-cli-irsa-test -n $NAMESPACE | grep AWS_WEB_IDENTITY_TOKEN_FILE
Deberíamos ver algo similar a esto:
AWS_ROLE_ARN: arn:aws:iam::123456789012:role/eks-irsa-s3-read-role
AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
Estas variables son parte del mecanismo que permite que la AWS CLI o los SDK compatibles usen el token de la ServiceAccount para obtener credenciales temporales desde AWS STS.
Conclusión
Las ServiceAccount son un recurso fundamental cuando trabajamos con Amazon EKS, porque nos permiten asignar una identidad específica a los Pods.
Cuando combinamos ServiceAccount con IRSA, podemos conectar esa identidad de Kubernetes con un rol de IAM y controlar de forma precisa a qué servicios de AWS puede acceder cada aplicación.
La idea central es simple:
Un Pod no debería heredar permisos amplios del nodo.
Un Pod debería tener su propia identidad.
Esa identidad debería tener solo los permisos necesarios.
Como buena práctica, cada aplicación que necesite acceder a servicios de AWS debería tener su propia ServiceAccount y su propio IAM Role con permisos mínimos.
De esta forma logramos un clúster EKS más seguro, más ordenado y más fácil de auditar.

