Skip to content

IRSA: доступ к aws api из Pod-а EKS

Представим ситуацию, что нашему приложению, работающему в контейнере Pod-а EKS кластера нужно получить доступ к приватному s3 бакету в этом же аккаунте

Рассмотрим варианты как это можно сделать

IAM user

Преимущество этого варианта в том, что таким образом можно локально отлаживать приложение

Для общения с aws api используется какое-то SDK, в его клиент нужно передать содержимое aws_access_key_id aws_secret_access_key от IAM пользователя, у которого есть права на ресурс

Со стороны AWS IAM ресурсы сконфигурированные следующим образом:

AWS configuration

resource "aws_iam_policy" "this" {
  name   = "s3-app"
  policy = <<EOF
{
  "Version":"2012-10-17",
  "Statement":[
      {
        "Sid":"",
        "Effect":"Allow",
        "Action": "s3:*",
        "Resource":[
            "arn:aws:s3:::${aws_s3_bucket.this.bucket}",
            "arn:aws:s3:::${aws_s3_bucket.this.bucket}/*"
        ]
      }
  ]
}
EOF
}
resource "aws_iam_user" "this" {
  name = "s3-manager"
}
resource "aws_iam_user_policy_attachment" "this" {
  user       = aws_iam_user.this.name
  policy_arn = aws_iam_policy.this.arn
}

k8s configuration

---

apiVersion: apps/v1
kind: Deployment
...
spec:
  ...
  template:
    ...
    spec:
      containers:
        - name: s3-app
          ...
          env:
            - name: AWS_ACCESS_KEY_ID
              valueFrom:
                secretKeyRef:
                  name: s3-app
                  key: aws_access_key_id
            - name: AWS_SECRET_ACCESS_KEY
              valueFrom:
                secretKeyRef:
                  name: s3-app
                  key: aws_secret_access_key

IRSA

IRSA - IAM Roles for Service Accounts, это установление доверительных отношений между IAM Role в AWS, и ServiceAccount-ом в EKS кластере через OIDC провайдер EKS

К IAM Role можно применить IAM Policy, точно такую же как к IAM user из предыдущего пункта, таким образом Pod использующий ServiceAccount получит все необходимые credentials во все контейнеры

AWS configuration

Со стороны IAM это конфигурируется так:

resource "aws_iam_policy" "this" {
  name   = "s3-app"
  policy = <<EOF
{
  "Version":"2012-10-17",
  "Statement":[
      {
        "Sid":"",
        "Effect":"Allow",
        "Action": "s3:*",
        "Resource":[
            "arn:aws:s3:::${aws_s3_bucket.this.bucket}",
            "arn:aws:s3:::${aws_s3_bucket.this.bucket}/*"
        ]
      }
  ]
}
EOF
}
module "iam_assumable_role_s3_app" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
  version = "4.2.0"

  create_role = true
  role_name   = "s3-app"

  provider_url = replace(module.eks_cluster.cluster_oidc_issuer_url, "https://", "")

  role_policy_arns = [
    aws_iam_policy.this.arn
  ]

  // NOTE: формат записи траста ServiceAccount
  // system:serviceaccount:<ServiceAccount.metadata.namespace>:<ServiceAccount.metadata.name>
  oidc_fully_qualified_subjects = [
    "system:serviceaccount:dev:s3-app",
  ]
}

На случай если имя ServiceAccount динамическое

oidc_subjects_with_wildcards = [
  "system:serviceaccount:crossplane-system:aws-provider-*"
]

⚠️ нельзя одновременно использовать две директивы oidc_fully_qualified_subjects и oidc_subjects_with_wildcards

k8s configuration

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-app
  namespace: dev
  annotations:
    eks.amazonaws.com/role-arn: 'arn:aws:iam::<aws_account_id>:role/s3-app' # (1)
  1. ARN IAM Role, которую создал module iam-assumable-role-with-oidc
---

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  ...
...
spec:
  ...
  template:
    ...
    spec:
      serviceAccountName: s3-app
      containers:
        - name: s3-app
          ...

Проверка

Если exec-нуться в Pod, можно обнаружить такие переменные окружения

env | grep AWS
AWS_DEFAULT_REGION=<aws_region>
AWS_REGION=<aws_region>
AWS_ROLE_ARN=arn:aws:iam::<aws_account_id>:role/s3-app
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token

Если разобрать токен из файла /var/run/secrets/eks.amazonaws.com/serviceaccount/token на https://jwt.io/ получим следующее

{
  "aud": [
    "sts.amazonaws.com"
  ],
  "exp": 1673637993,
  "iat": 1673551593,
  "iss": "https://oidc.eks.<aws_region>.amazonaws.com/id/<HEX_HASH>",
  "kubernetes.io": {
    "namespace": "test",
    "pod": {
      "name": "s3-app-<replicaSet>-<random-hash>",
      "uid": "47a2e254-d0d3-44bc-acdc-ef8f07a9f63e"
    },
    "serviceaccount": {
      "name": "s3-app",
      "uid": "91bb19bc-dab8-4c72-9718-eac7adbc4b59"
    }
  },
  "nbf": 1673551593,
  "sub": "system:serviceaccount:dev:s3-app"
}

Проверяем доступ к бакету

aws s3 ls --recursive s3://bucket-s3-app
2021-12-14 15:07:52   47134393 app/my-cool.stuff
...

Достигается это с помощью вебхука (github репозиторий)

Cross account irsa

Представим ситуацию, что нашему приложению, работающему в контейнере Pod-а EKS кластера AWS аккаунта A нужно получить доступ к приватному s3 бакету в AWS аккаунте B

Примерная схема, где CI account = A, Target Account = B:

img

Warning

В большинстве ситуаций мы должны стараться избегать ситуации, когда из Pod-а в аккаунте A, нам нужен доступ к AWS ресурсу в аккаунте B через AWS api

AWS account A

AWS configuration

resource "aws_iam_policy" "this" {
  name   = "s3-app"
  policy = <<EOF
// Политика разрешает брать на себя роль из аккаунта B
{
  "Version":"2012-10-17",
  "Statement":[
      {
        "Action":"sts:AssumeRole",
        "Resource":"arn:aws:iam::<aws_account_id_B>:role/s3-app-B",
        "Effect":"Allow",
        "Sid":""
      }
  ]
}
EOF
}
locals {
  oidc = replace(module.eks_cluster.cluster_oidc_issuer_url, "https://", "")
}

resource "aws_iam_role" "this" {
  name = "s3-app-A"

  assume_role_policy = <<EOF
// Устанавливаем Trust, между ролью аккаунта А и SA в EKS
{
  "Version":"2012-10-17",
  "Statement":[
      {
        "Effect":"Allow",
        "Principal":{
            "Federated":"arn:aws:iam::<aws_account_id_A>:oidc-provider/${local.oidc}"
        },
        "Action":"sts:AssumeRoleWithWebIdentity",
        "Condition":{
            "StringEquals":{
              "${local.oidc}:sub":"system:serviceaccount:dev:s3-app"
            }
        }
      }
  ]
}
EOF
}
resource "aws_iam_role_policy_attachment" "this" {
  role       = aws_iam_role.this.name # (2)
  policy_arn = aws_iam_policy.this.arn # (1)
}

k8s configuration

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-app
  namespace: dev
  annotations:
    eks.amazonaws.com/role-arn: 'arn:aws:iam::<aws_account_id_A>:role/s3-app-A' # (1)
  1. ARN IAM Role, которая доверяет этому SA, и может применять роль из аккаунта B
---

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: dev
  ...
...
spec:
  ...
  template:
    ...
    spec:
      serviceAccountName: s3-app
      containers:
        - name: s3-app
          ...

AWS account B

resource "aws_iam_policy" "this" {
  name   = "s3-app"
  policy = <<EOF
{
  "Version":"2012-10-17",
  "Statement":[
      {
        "Sid":"",
        "Effect":"Allow",
        "Action": "s3:*",
        "Resource":[
            "arn:aws:s3:::${aws_s3_bucket.this.bucket}",
            "arn:aws:s3:::${aws_s3_bucket.this.bucket}/*"
        ]
      }
  ]
}
EOF
}
resource "aws_iam_role" "this" {
  name   = "s3-app-B"
  assume_role_policy = <<EOF
// Gotcha: тут устанавливаем Trust, между ролью аккаунта B и ролью в аккаунте А
{
  "Version":"2012-10-17",
  "Statement":[
      {
        "Effect":"Allow",
        "Principal":{
            "AWS":"arn:aws:iam::<aws_account_id_A>:role/s3-app-A"
        },
        "Action":"sts:AssumeRole"
      }
  ]
}
EOF
}
resource "aws_iam_role_policy_attachment" "this" {
  role       = aws_iam_role.this.name
  policy_arn = aws_iam_policy.this.arn
}

Проверка

Если exec-нуться в Pod, можно обнаружить все те же переменные окружения

# env
env | grep AWS
AWS_DEFAULT_REGION=<aws_region>
AWS_REGION=<aws_region>
AWS_ROLE_ARN=arn:aws:iam::<aws_account_id_A>:role/s3-app-A
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token

В jwt мы увидим такую же картину

При попытки доступа к s3 бакету получим ошибку

aws s3 ls --recursive s3://bucket-s3-app
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

Добавим следующий конфиг

cat <<EOF >>~/.aws/config
[profile A]
role_arn = arn:aws:iam::<aws_account_id_A>:role/s3-app-A
web_identity_token_file = /var/run/secrets/eks.amazonaws.com/serviceaccount/token
[profile B]
role_arn = arn:aws:iam::<aws_account_id_B>:role/s3-app-B
source_profile = A
role_session_name = xactarget
EOF

Пробуем получить данные с указанием profile-а

aws s3 ls --recursive s3://bucket-s3-app --profile B
2021-12-14 15:07:52   47134393 app/my-cool.stuff
...

Готово 🔥

Ссылки

Comments