top of page

Acceso entre cuentas a S3 desde EKS usando IRSA con Terraform como IaaC

  • Foto del escritor: by Leandro Mansilla
    by Leandro Mansilla
  • 2 ago
  • 5 Min. de lectura

Tenemos muchas opciones para obtener acceso entre cuentas a recursos, pero cuando hablamos del clúster de Kubernetes, ¡las cosas pueden ponerse un poco complicadas! Así que, en este blog, compartiré una solución para hacerlo de la forma más segura utilizando el principio de menor privilegio.


ree

Un escenario típico es tener dos cuentas, la Cuenta A, con un clúster EKS, y la Cuenta B con un bucket S3 (example_bucket) al que necesita acceder un pod desde la Cuenta A.Tenemos muchas opciones para esto:


  • Podemos crear una política de bucket con el nombre del rol de los workers del clúster de Kubernetes.

  • Podemos crear un rol de IAM en la Cuenta B, otorgar al rol permisos para realizar operaciones necesarias en S3, asumir el rol con una política de confianza, etc...


Esas son algunas soluciones para acceder al bucket; sin embargo, al obtener acceso de esa manera, otorgamos muchos privilegios que no necesitamos porque solo necesitamos dar acceso a un pod, no a todo el clúster.Por eso, AWS nos proporciona los IAM Roles for Service Accounts (IRSA).

IRSA nos permite asociar un rol de IAM con una service account de Kubernetes, y esta service account puede otorgar permisos a cualquier pod que la use.


Usar IRSA tiene el beneficio de seguir la recomendación de menor privilegio y el aislamiento de credenciales, lo que significa que el contenedor dentro del pod solo puede recuperar credenciales para el rol de IAM asociado con la service account a la que pertenece el pod.


Para que IRSA funcione, necesitamos lo siguiente:

  • Proveedor IAM OIDC

  • El rol IAM

  • Y finalmente, asociar el rol IAM con la service account de Kubernetes.


Entonces, vamos a otorgar acceso a un pod desde un clúster EKS en la Cuenta A para obtener un objeto de un bucket S3 en la Cuenta B usando IRSA con acceso entre cuentas.


El siguiente diagrama muestra lo que vamos a hacer:


Vamos a usar Terraform como infraestructura como código (IaaC) para este ejemplo, YAML de Kubernetes para el deployment y una service account de Kubernetes.

""

We‘re going to use Terraform as IaaC for this example and Kubernetes YAML for deployment, and Kubernetes service account.


En la Cuenta A: Desplegaremos un pod con una service account que permitirá que ese pod obtenga objetos del bucket ubicado en la Cuenta B.


En la Cuenta B: Crearemos un rol IAM en la Cuenta B con un ARN de proveedor OIDC IAM como fuente de confianza para permitir que la Cuenta A asuma el rol. Este recurso es el resultado de usar la URL del emisor OIDC del clúster de Kubernetes de la Cuenta A; luego, adjuntaremos las políticas necesarias para acceder al bucket S3.


Comencemos primero con la Cuenta B.

Asumimos que tenemos un archivo test.txt dentro de un bucket llamado test-oidc-cross-account-bucket en la Cuenta B.


Paso 1: Crear el proveedor IAM OIDC

Lo primero que necesitamos es la URL del emisor OIDC del clúster de la Cuenta A para generar el proveedor IAM OIDC en la Cuenta B; puedes obtenerla desde el output del módulo terraform de EKS, cluster_oidc_issuer_url.

Desde la consola terraform en la Cuenta A:


➜ terraform console
Acquiring state lock. This may take a few moments...
> module.eks.cluster_oidc_issuer_url
"https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE1111222EAEEE"

Luego en la Cuenta B, crea el proveedor IAM OIDC:


resource "aws_iam_openid_connect_provider" "oidc_issuer" {
  url             = "https://oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE1111222EAEEE"
  thumbprint_list = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"]
  client_id_list  = ["sts.amazonaws.com"]
}

También puedes obtener el thumbprint desde el módulo terraform de EKS.


Paso 2: Crear el rol IAM con la relación de confianza y adjuntar la política para obtener acceso a S3


Primero, creamos la política de assume-role que establece la relación de confianza.

Los principales usarán el ARN del aws_iam_openid_connect_provider que creamos en el primer paso como identificador.


En la condición, evaluaremos StringEquals con la URL OIDC (sin https://) del clúster de la Cuenta A, y en los valores usaremos el nombre de la service account de Kubernetes en la forma system:serviceaccount:<namespace>:<nombre_de_la_service_account>.


data "aws_iam_policy_document" "irsa" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type = "Federated"
      identifiers = [
        aws_iam_openid_connect_provider.oidc_issuer.arn #arn OIDC 
      ]
    }
    condition {
      test     = "StringEquals"
      variable = "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE1111222EAEEE:sub" 
      values = [
        "system:serviceaccount:default:s3-sa"
      ]
    }
  }
}

La política S3:

data "aws_iam_policy_document" "s3" {
  statement {
    sid    = ""
    effect = "Allow"
    actions = [
      "s3:ListBucket"
    ]
    resources = [
      aws_s3_bucket.test_bucket.arn,
      "${aws_s3_bucket.test_bucket.arn}/*",
    ]
  }
  statement {
    sid    = ""
    effect = "Allow"
    actions = [
      "s3:GetObject",
    ]
    resources = [
      aws_s3_bucket.test_bucket.arn,
      "${aws_s3_bucket.test_bucket.arn}/*",
    ]
  }
}

Luego, la creación de los recursos:

resource "aws_iam_policy" "s3" {
  name   = "s3_read"
  path   = "/"
  policy = data.aws_iam_policy_document.s3.json
}
 
resource "aws_iam_role" "s3" {
  name               = "s3"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.irsa.json
}
 
resource "aws_iam_role_policy_attachment" "s3" {
  role       = aws_iam_role.s3.name
  policy_arn = aws_iam_policy.s3.arn
}

Una vez que crees esos recursos, obtén el ARN del rol, lo necesitaremos en el paso final.

¡Genial! Terminamos todo el trabajo en la Cuenta B.


Paso 3: ¡Probar los permisos!

La siguiente y última tarea es crear una service account y un deployment de prueba que use esa service account para obtener permisos y acceder a un objeto del bucket. Para que esa service account se autentique contra la Cuenta B, debes agregar una anotación con el ARN del rol creado en el paso anterior en esta forma:


eks.amazonaws.com/role-arn: "arn:aws:iam::<Account_B_ID>:role/<role_name>" 

Creemos la service account:


apiVersion: v1
kind: ServiceAccount
metadata:
  name: "s3-sa" #Name we used during the role creation
  annotations:
    eks.amazonaws.com/role-arn: "arn:aws:iam::5554444333:role/s3" #role ARN
automountServiceAccountToken: true

Y un deployment de prueba que use esa service account:

Usé la imagen oficial de AWS CLI solo para ejecutar comandos desde el inicio.


➜ cat test_deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: test-deployment
  name: test-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-deployment
  template:
    metadata:
      labels:
        app: test-deployment
    spec:
      serviceAccountName: "s3-sa" #Name of the SA we ‘re using
      automountServiceAccountToken: true
      containers:
      - image: amazon/aws-cli
        name: aws
        command: ["sleep","10000"]

Aplica los YAML:

➜ kubectl apply -f s3_sa.yaml 
serviceaccount/s3-sa created
➜ kubectl apply -f test_deployment.yaml 
deployment.apps/test-deployment created

Verifica si está corriendo y luego ingresa al pod:

➜ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
test-deployment-7867876f54-xm5pm   1/1     Running   0          100s
 
➜ kubectl exec -it test-deployment-7867876f54-xm5pm -- bash
bash-4.2# 

Verifica las credenciales y verás que el pod asumió el rol que creamos en la Cuenta B; ¡éxito!

bash-4.2# aws sts get-caller-identity
{
    "UserId": "AROA2PKFSO4DYYGMNG4IS:botocore-session-1626387503",
    "Account": "5554444333",
    "Arn": "arn:aws:sts::5554444333:assumed-role/s3/botocore-session-1626387503"
}

¡Intentemos obtener algo del bucket!

bash-4.2# aws s3 ls s3://test-oidc-cross-account-bucket
2021-07-15 22:22:28         63 test.txt
 
bash-4.2# aws s3 cp s3://test-oidc-cross-account-bucket/test.txt .
download: s3://test-oidc-cross-account-bucket/test.txt to ./test.txt
 
bash-4.2# cat test.txt 
this is a test to prove you can get this file from this bucket 

En conclusión


IRSA es una buena práctica para gestionar permisos en pods por tres razones principales:


  • Usa el principio de menor privilegio.

  • Proporciona aislamiento de credenciales por pod.

  • Permite auditoría mediante CloudTrail.

""



Leandro Mansilla

DevOps Engineer

Teracloud















 
 
bottom of page