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.

¡Nos vemos en la próxima entrada!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.