AWS Elastic Kubernetes Service: RBAC-авторизация через AWS IAM и RBAC группы

Автор: | 04/12/2020
 

Имеется два новых проекта в Elastic Kubernetes Service (см. AWS: Elastic Kubernetes Service — автоматизация создания кластера, часть 1 — CloudFormation), каждый проект живёт в отдельном своём namespace.

Кроме того, имеется два пользователя, разработчика, которым надо дать доступ к этим двум пространствам имён, но только на поды в них и только на определённые read-only операции.

Что бы реализовать общий контроль доступа для этих пользователей — нам нужна некая группа, в которую мы сможем их объединить, а затем использовать её для проверки правил доступа.

Для понимания описанной ниже схемы — см. посты Kubernetes: знакомство, часть 4 — аутентификация в AWS EKS, aws-iam-authenticator и AWS IAM и Kubernetes: знакомство, часть 5 — RBAC авторизация и примеры Role и RoleBinding.

Что у нас есть?

У нас есть AWS IAM — его пользователи, группы, роли и политики.

И у нас есть Kubernetes RBAC с его ClusterRole, RoleBinding и aws-auth ConfigMap,

Значит, наша задача делится на две:

  • продумать механизм аутентификации и авторизации двух пользователей в AWS IAM, который сможет создать единый объект для идентификации (группу, роль)
  • продумать механизм авторизации двух пользователей в Kubernetes RBAC, используя этот объект

Проблема

В чём неочевидная сложность?

В том, как мы свяжем эти две задачи, так как в отличии от IAM Users и IAM Roles — AWS IAM Group не может являться субъектом аутентификации, см. список в AWS JSON Policy Elements: Principal, и мы не сможем использовать ARN такой группы вида arn:aws:iam::111:group/group-name в aws-auth ConfigMap, что бы дать пользователям этой группы доступ в Kubernetes кластер.

Зато — мы можем использовать IAM Roles, а через IAM Groups — можем подключать общие для пользователей IAM политики с доступами к нужным ролям.

Что мы можем построить, используя это?

  • создадим IAM роль с IAM политикой read-only на API-вызовы eks:* и ей добавим trust policy с пользователями нашего аккаунта — потом эту роль мы будем использовать в aws-auth ConfigMap
  • создадим IAM Group с IAM политикой, которая разрешает выполнение вызова sts::AssumeRole к роли, созданной выше
  • создадим IAM пользователя, добавим его в эту группу, что бы он через политику группы «наследовал» разрешение на AssumeRole

Немного заглянем вперёд — что будет дальше, в Kubernetes?

Там наш aws-auth ConfigMap «замапит» нашу IAM нашей роли в AWS на RBAC-роль в кластере, а нашим пользователям мы настроим AWS CLI на выполнение AssumeRole собственно роли arn:aws:iam::534***385:role/iam-bttrm-web-ro-role — тогда в Kubernetes будет приходить общий для них обоих индентификатор, и пользователи смогут выполнять операции, заданные в их общей роли и только в заданных нами namespaces.

Про имена

Итак, у нас есть два пользователя — объединим их в условную группу «web«, которая будет использоваться в именах IAM ролей и групп.

Плюс у нас есть два проекта в Kubetenets с отдельными namespaces — project1 и project2, которые мы тоже объединим в условную группу «web«, но эту группу используем только в именах RBAC-ресурсов в Kuberenetes для большей их наглядности.

Кроме того — к именам ресурсов AWS IAM будем добавлять префикс iam-, а дял RBAC, соответсвенно — rbac-.

Следовательно, у нас будут имена вида:

  • iam-bttrm-web-ro-role: тут iam говорит, что это объект из AWS IAM, bttrm — общее имя моего проекта, web — «домен» для наших двух новых веб-проектов
  • rbac-bttrm-web-ro-role-binding: rbac — объект связан с Kubernetes RBAC, bttrm — общее имя моего проекта, web — «домен» для наших двух веб-проектов

AWS IAM

Что бы представлять — что именно мы делаем, и как оно всё друг с другом будет работать — можно набросать такую вот схему:

 

Тут:

  1. пользователю требуется получить доступ к роли, для чего он выполняет AssumeRole-запрос в AWS IAM:
    1. AWS IAM проводит аутентификацию пользователя
    2. если аутентификация пройдена — AWS IAM начинаего авторизацию пользователя: проверяет политики доступа, подключенные к нему
    3. находит политику, разрешающую AssumeRole нужной роли и разрешает пользователю обращение к этой роли
  2. пользователь обращается к IAM-роли
    1. AWS IAM проверяет Trust relations этой роли
    2. убеждается, что пользователь пришёл из того же аккаунта, что наша роль и разрешает доступ
  3. пользователь обращается в AWS Elastic Kubenetes Service, но уже используя индентификатор (токен) IAM-роли

Эта и следующая схема делалась в cloudcraft.co, но в рабочем процессе подобные просто рисуются от руки в большом блокноте.
Не знаю, почему ни в одном встреченном гайде никто не отобразил все эти связи в виде таких вот простых и наглядных схем — без них делать всё это намного сложнее, особенно по части Kubernetes RBAC.

Пока оставим её тут — по ходу дела можно будет вернутся.

IAM Role

Создаём новую роль — ключевой, по сути, элемент всей конструкции.

Тип ЕС2:

На следующей странице жмём Create policy, в новой вкладке в окне создания политики переключаемся на JSON, вносим политику:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "eks:DescribeCluster",
                "eks:ListClusters"
            ],
            "Resource": "*"
        }
    ]
}

В нёй мы разрешаем выполнение API-запросов eks:DescribeCluster и eks:ListClusters по всем регионам всего нашего аккаунта — эти вызовы используются AWS CLI во время выполнения aws eks update-kubeconfig, которую будут выполнять наши новые пользователи для настройки своих локальных kubectl.

Сохраняем её с именем iam-bttrm-eks-ro-policy.

Возвращаемся в предыдущую вкладку с созданием роли, справа кликаем на «Обновить», и добавляем к роли созданную политику:

Tags пропускаем, сохраняем роль с именем iam-bttrm-web-ro-role:

Запоминаем её Role ARN:

IAM Role Trust relationships

Хотя вот тут>>> в ответах утверждают, что Trust relations в рамках одного аккаунта создавать не требуется — но у меня без него не работало, плюс он упоминается в документации вот тут>>>.

Переключаемся во вкладку Trust relationships, жмём Edit trust relationship, в Principal указываем «AWS»: «arn:aws:iam::534***385:root» — тут root включает в себя всех пользователей (точнее — всех объектов аутентифицикации) нашего аккаунта:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::534***385:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Но это ещё не значит, что любой субъект, прошедший аутентификацию в нашем аккаунте сможет использовать («assu-мить«) эту роль, т.к. у этого субъекта ещё должны быть соответсвующие права на вызов этой конкретно этой роли — и для наших будущих пользователей мы это как раз и реализуем через IAM Group.

IAM Policy

Перед тем, как создавать группу — добавим собственно политику, которая будет разрешать выполнение выполнение API-вызова sts:AssumeRole к роли, которую мы создали, а потом эту политику подключим к создаваемой группе, через которую она «унаследуется» будущими пользователями группы.

В свою очередь, что бы отобрать у IAM-пользователя доступ к Elastic Kubernetes Service и подам в нём — достаточно будет отключить его от группы.

Создаём политику:

В Resource указываем ARN политики, которую создали выше:

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::534***385:role/iam-bttrm-web-ro-role"
    }
}

Сохраняем её, например с именем iam-bttrm-allow-assume-web-ro-role-policy:

IAM Group

Переходим в Группы, создаём новую, назовём её iam-bttrm-web-ro-group, подключаем ей созданную раннее политику:

IAM User

Создаём пользователя с Programmatic access, добавляем его к группе iam-bttrm-web-ro-group, к которой подключена политика iam-bttrm-allow-assume-web-ro-role-policy:

Настраиваем локальный AWS CLI профиль на нового пользователя iam-bttrm-web-user-1:

aws configure --profile iam-bttrm-web-user-1
AWS Access Key ID [None]: AKI***O4Z
AWS Secret Access Key [None]: FoS***kft
Default region name [None]: us-east-2
Default output format [None]: json

Проверяем:

aws --profile iam-bttrm-web-user-1 sts get-caller-identity
{
"UserId": "AID***DUH",
"Account": "534***385",
"Arn": "arn:aws:iam::534***385:user/iam-bttrm-web-user-1"
}

Пробуем доступ к ЕС2 — должны получить отказ:

aws --profile iam-bttrm-web-user-1 ec2 describe-instances
An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation.

Хорошо — разрешения на API-запрос DescribeInstances нам никто не давал, и авторизацию мы не прошли (хотя прошли аутентификацию как пользователь iam-bttrm-web-user-1).

Пробуем EKS-операции:

aws  --profile iam-bttrm-web-user-1 eks list-clusters
An error occurred (AccessDeniedException)when calling the ListClusters operation:
User: arn:aws:iam::534***385:user/iam-bttrm-web-user-1 is not authorized to perform: eks:ListClusters on resource: arn:aws:eks:us-east-2:534***385:cluster/*

Отлично — мы снова прошли аутентификацию как iam-bttrm-web-user-1 — но снова не прошли авторизацию, в этот раз на выполнение API-вызова eks:ListClusters.

А теперь — используем AssumeRole:

  1. пройдём аутентификацию, как iam-bttrm-web-user-1
  2. запросим sts:AssumeRole — IAM проверит, какие политики к нам подключены и найдёт политику iam-bttrm-allow-assume-web-ro-role-policy
  3. мы временно получим права на выполнение операций, разрешённых для роли iam-bttrm-web-ro-role, среди которых — наш eks:ListClusters

Пробуем.

Обновляем локальный ~/.aws/config, и к профилю iam-bttrm-web-user-1 добавим второй профиль — iam-bttrm-web-user-1-eks: (см. быстрый пример работы с AssumeRole в AWS: IAM AssumeRole — описание, примеры):

...
[profile iam-bttrm-web-user-1]
region = us-east-2
output = json

[profile iam-bttrm-web-user-1-eks]
role_arn = arn:aws:iam::534***385:role/iam-bttrm-web-ro-role
source_profile = iam-bttrm-web-user-1
region = us-east-2

В котором мы используем роль arn:aws:iam::534***385:role/iam-bttrm-web-ro-role, но через source_profile аутентифицируемся как iam-bttrm-web-user-1. (см. Using an IAM Role in the AWS CLI).

Пробуем — повторяем под обычным профилем:

aws  --profile iam-bttrm-web-user-1 eks list-clusters --output text
An error occurred (AccessDeniedException) [...]

И под вторым, с AssumeRole:

aws  --profile iam-bttrm-web-user-1-eks eks list-clusters --output text
CLUSTERS        bttrm-eks-prod-0
CLUSTERS        eksctl-bttrm-eks-production-1
CLUSTERS        bttrm-eks-dev-0

Всё работает, с этим закончили — у нас есть группа, используя которую пользователи получают возможность выполнять eks:ListClusters.

Используя эту же роль (но свои ACCESS и SECRET ключи для аутентификации) — они настроят свой kubectl для доступа к подам. А вот доступ к неймспейсам и подам мы ограничим через Kubernetes RBAC, см. .

Kubernetes RBAC

Итак, нам требуется иметь некую группу, через права доступа которой мы сможем выдать доступ к подам в двух различных неймспейсах, при этом эта группа должна быть связана с группой в AWS IAM.

Так как AWS IAM Group ARN мы использовать не можем — то делаем «грязный хак» в виде использования общей роли для разных пользователей группы, по которой мы и будем их идентифицировать для авторизации в Kubernetes RBAC.

Далее нам надо создать:

  • ClusterRole — роль, которая будет разрешать операции с подами во всех namespace
  • две RoleBinding в двух namespace — по одной в каждой, которые будут связывать в этом пространстве имён RBAC-группу и ClusterRole, таким образом ограничивая влияние этой ClusterRole для RBAC-группы границами namespace, в которой эта RoleBinding создана
  • и обновим aws-auth ConfigMap, что бы он связал IAM Role и RBAC-группу

В результате у нас должна получиться следующая схема:

RBAC ClusterRole

Описываем ClusterRole с именем rbac-bttrm-pods-ro-cluster-role:

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rbac-bttrm-pods-ro-cluster-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

Создаём её:

kubectl apply -f rbac-bttrm-pods-ro-cluster-role.yml
clusterrole.rbac.authorization.k8s.io/rbac-bttrm-pods-ro-cluster-role created

RBAC RoleBinding

Создаём два RoleBinding — одинаковых, но в разных namespacebttrm-web-proj-1-ns и bttrm-web-proj-2-ns:

--
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rbac-bttrm-web-proj-1-ro-role-binding
  namespace: bttrm-web-proj-1-ns
subjects:
- kind: Group
  name: rbac-bttrm-web-ro-group
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: rbac-bttrm-pods-ro-cluster-role
  apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rbac-bttrm-web-proj-2-ro-role-binding
  namespace: bttrm-web-proj-2-ns
subjects:
- kind: Group
  name: rbac-bttrm-web-ro-group
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: rbac-bttrm-pods-ro-cluster-role
  apiGroup: rbac.authorization.k8s.io

Создаём сами неймспейсы:

kubectl create ns bttrm-web-proj-1-ns
namespace/bttrm-web-proj-1-ns created
kubectl create ns bttrm-web-proj-2-ns
namespace/bttrm-web-proj-2-ns created

Создаём в них поды с NGINX:

kubectl -n bttrm-web-proj-1-ns run nginx --image=nginx
kubectl -n bttrm-web-proj-2-ns run nginx --image=nginx

И создаём биндинги:

kubectl apply -f rbac-bttrm-web-ro-role-binding.yml
rolebinding.rbac.authorization.k8s.io/rbac-bttrm-web-proj-1-ro-role-binding created
rolebinding.rbac.authorization.k8s.io/rbac-bttrm-web-proj-2-ro-role-binding created

aws-auth ConfigMap

Теперь нам надо обновить aws-auth ConfigMap, что бы связать нашу группу в AWS (а по сути — общую роль) с «виртуальной» группой rbac-bttrm-web-ro-group, которую мы «создали» в биндингах.

Редактируем её:

kubectl -n kube-system edit cm aws-auth

Добавляем в mapGroups:

...
    - groups:
      - rbac-bttrm-web-ro-group
      rolearn: arn:aws:iam::534***385:role/iam-bttrm-web-ro-role
      username: iam-bttrm-web-ro-role 
...

Сохраняем, выходим.

kubecl config и проверка

Теперь для нашего kubectl настроим контекст доступа к нашему дев-кластеру, используя AWS CLI профиль iam-bttrm-web-user-1-eks, в котором выполняется AssumeRole arn:aws:iam::534***385:role/iam-bttrm-web-ro-role, при этом для аутентфикации, как помним, используется IAM пользователь iam-bttrm-web-user-1:

aws --profile iam-bttrm-web-user-1-eks eks update-kubeconfig --name bttrm-eks-dev-0
Updated context arn:aws:eks:us-east-2:534***385:cluster/bttrm-eks-dev-0 in /home/setevoy/.kube/config

Пробуем просто get pod в default namespace:

kubectl get pod
Error from server (Forbidden): pods is forbidden: User "iam-bttrm-web-ro-role" cannot list resource "pods" in API group "" in the namespace "default"

И повторяем в bttrm-web-proj-1-ns:

kubectl get pod -n bttrm-web-proj-1-ns
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7bb7cd8db5-l9jtm   1/1     Running   0          2m24s

Вот наш NGINX.

Повторяем для неймспейса второго проекта — bttrm-web-proj-2-ns:

kubectl get pod -n bttrm-web-proj-2-ns
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7bb7cd8db5-9s6zj   1/1     Running   0          2m31s

Можно попробовать доступы к другим ресурсам, доступа к которым мы не давали, например — к нодам:

kubectl get node
Error from server (Forbidden): nodes is forbidden: User "iam-bttrm-web-ro-role" cannot list resource "nodes" in API group "" at the cluster scope

Или использовать auth can-i:

kubectl auth can-i get node
Warning: resource 'nodes' is not namespace scoped
no
kubectl auth can-i get pod
no
kubectl auth can-i get pod -n bttrm-web-proj-1-ns
yes
kubectl auth can-i create pod -n bttrm-web-proj-1-ns
no

Готово.

Ссылки по теме