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
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 это конфигурируется так:
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_fully_qualified_subjects
и oidc_subjects_with_wildcards
k8s configuration
Проверка
Если 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"
}
Проверяем доступ к бакету
Достигается это с помощью вебхука (github репозиторий)
Cross account irsa
Представим ситуацию, что нашему приложению, работающему в контейнере Pod-а EKS кластера AWS аккаунта A нужно получить доступ к приватному s3 бакету в AWS аккаунте B
Примерная схема, где CI account = A, Target Account = B:
Warning
В большинстве ситуаций мы должны стараться избегать ситуации, когда из Pod-а в аккаунте A, нам нужен доступ к AWS ресурсу в аккаунте B через AWS api
AWS account A
AWS configuration
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
}
k8s configuration
AWS account B
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
}
Проверка
Если 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
...
Готово