Kubernetes: знакомство, часть 4 – аутентификация в AWS EKS, aws-iam-authenticator и AWS IAM

Автор: | 31/08/2019
 

Продолжаем погружение в AWS Elastic Kubernetes Service, EKS.

Предыдущие части:

Продолжение:

В предыдущем посте серии – Kubernetes: знакомство, часть 3 — обзор AWS EKS и ручное создание кластера – мы запустили свой EKS кластер – всё отлично.

kubectl работает, ресурсы создаются.

Но когда после выполнения aws eks update-kubeconfig для настройки kubectl на своём маке наш бекенд-девелопер попытался подключиться:

[simterm]

root@ip-10-0-42-255:~# kubectl get nodes 
error: You must be logged in to the server (Unauthorized)

[/simterm]

Казалось бы – оба IAM пользователя с админ правами в AWS Console – что могло пойти не так?

Потому – начинаем искать причину, которая снова заставляет немного углубиться в процесс аутентификации и авторизации.

В топике ниже рассмотрим:

  • аутентификация vs авторизация
  • модули и процесс аутентификации пользователя в EKS – aws-iam-authenticator, AWS AIM

А в следующей части – авторизацию и RBAC в Kubernetes.

Authentication vs Authorization

Для начала – поговорим о разнице между понятиями аутентификация и авторизация пользователя.

Authentication

Аутентификация – это процесс, при котором клиент должен доказать серверу, что что он именно тот, за кого себя выдаёт.

Например, после создания EKS мы поднимаем рабочие ноды, которые затем обращаются к API-серверу Kubernetes Control Plane, что бы подключиться к кластеру.

При этом – API-сервер должен иметь возможность проверить – что за клиент к нему обратился, и имеет ли он вообще право к нам обращаться.

Для этого у Kubernetes имеются authentication modules или authenticators: когде API-сервер получается запрос от клиента, будь то новая Worker Node, kubelet или просто API-запрос, отправленный curl-ом – он обращается к одному из настроенных у Kubernetes аутентификаторов и запускает процесс аутентификации клиента, отправившего запрос.

Все доступные модули можно увидеть в документации, но нас сейчас интересует один, использующийся в AWS Elatic Kubernetes Service – aws-iam-authenticator для проверки валидности пользователя, используя сервис AWS IAM, который мы будем рассматривать ниже.

Authorization

После того, как пользователь прошёл аутентификацию, Kubernetes должен проверить сам запрос – есть ли у данного клиента право на выполнение запрошенных действий, например – выполнение вызова kubectl get pods.

Если вспомнить IAM-политики, то в них мы явно описываем права на конкретные сервисы и действия, например:

Тут в Action указаны действия, разрешённые пользователю, к которому эта политика подключена, и при вызове им операции “s3:DeleteObject” – пользователь получит отказ на выполнение действия, т.е. проверяется контроль доступа к API-вызовам (а вся работа с AWS, как и с Kubernetes, выполняется через API-вызовы к ядру AWS).

Аналогично аутентификации – у Kubernetes есть authorisation modules или authorisers, например – Attribute-based access control (ABAC) и Role-based access control (RBAC).

Модулю авторизации передаётся пользователь, прошедший аутентификацию, после чего он проходит авторизацию, на основании которой API-сервер принимает решение о выполнении действия – или отказе.

Смотрите документацию тут – Controlling Access to the Kubernetes API.

Далее рассмотрим весь процесс аутентификации и авторизации в AWS EKS, а потом перейдём к авторизации.

AWS EKS Authentication и Authorization

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

AWS EKS IAM Authentication

Для аутентификации EKS использует токены – см. Webhook Token Authentication: клиент передаёт API-серверу специально сформированный токен аутентификации, в котором среди прочих данных передаётся идентификатор пользователя.

В случае с EKS – этим идентификатором могут являться ARN (Amazon Resource Name) либо IAM-пользователя, либо IAM-роли.

Kubrnetes-аутентификатор в свою очередь передаёт извлечённый идентификатор самому AWS IAM для проверки – есть ли такой, и имеет ли право говорить с нами, а в роли аутентификатора в Kubernetes использует AWS IAM Authenticator.

Т.е. процесс выглядит следующим образом:

  • клиент выполняет запрос к API-серверу, передавая токен аутентификации, в котором включён ID пользователя
  • API-сервер передаёт токен другому сервису Kubernetes Control Plane – aws-iam-authenticator
  • aws-iam-authenticator обращается к AWS IAM, передавая этого пользователя для проверки – имеется ли такой IAM-идентификатор (ARN) и имеет ли он право доступа к запрашиваемому EKS-кластеру
    • у себя AWS проводит собственную аутентификацию, используя секретный ключ, связанный с предоставленным ACCESS_ID,
    • и проводит собственную авторизацию на соответствие политикам, подключенным к этом пользователю – пользователь без прав на API-вызовы к eks::* должен быть отвергнут
  • aws-iam-authenticator обращается к Kubernetes, проверяя у него (через aws-auth ConfigMap, будет в AWS EKS aws-auth ConfigMap) – имеет ли этот IAM-индентификатор право на доступ к кластеру
  • aws-iam-authenticator возвращает ответ валиден или нет клиент API-серверу
  • API-сервер отвечает клиенту ответом с данными, либо сообщает You must be logged in to the server (Unauthorized) и отвергает запрос

Примечание: ранее использовался встроенный в сам Kubernetes механизм AWS cloud-provider, был в Kubernetes: знакомство, часть 2 — создание кластера с AWS cloud-provider и AWS LoadBalancer.

При этом сам aws-iam-authenticator может использоваться как на сервере – так и на клиенте, только на сервере он запускается как aws-iam-authenticator server, а на клиенте –  aws-iam-authenticator token -i:

Но в моём случае клиент настраивался через aws eks update-kubeconfig и использует AWS CLI вместо вызова aws-iam-authenticator (см.  AWS CLI vs aws-iam-authenticator).

Аутентификация kubectl

Используем kubectl на локальной машине в роли клиента, и посмотрим всю цепочку сами.

Итак, kubectl обращается к локальному файлу ~/.kube/config, из которого получает URL API-сервера EKS-кластера, к которому ему следует обращаться:

...
server: https://715***834.sk1.us-east-2.eks.amazonaws.com
...

Далее, нас интересует часть в exec, а именно – command и args:

...
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - --region
      - us-east-2
      - eks
      - get-token
      - --cluster-name
      - mobilebackend-dev-eks-0-cluster
      command: aws
...

Которая собственно и описывает команду для получения токена для аутентификации в EKS, в котором используется AWS CLI – aws eks get-token.

Очень хочется покопаться в аутентификации самого AWS API, например быстро нагугленные решения есть в Developer Guide для AWS S3, если будет время – можно будет попробовать сделать всю работу просто используя curl, т.к. под капотом AWS CLI точно так же обращается к AWS по API, как наш kubectl – к API-серверу Kubernets.

Вызываем команду из command, передавая аргументы из args:

[simterm]

$ aws --profile arseniy --region us-east-2 eks get-token --cluster-name mobilebackend-dev-eks-0-cluster
{"kind": "ExecCredential", "apiVersion": "client.authentication.k8s.io/v1alpha1", "spec": {}, "status": {"expirationTimestamp": "2019-08-31T10:27:24Z", "token": "k8s-aws-v1.aHR...zEy"}}

[/simterm]

“token”: “k8s-aws-v1.aHR…zEy” – токен получен.

Содержимое можно просмотреть например в https://www.base64decode.org (вставляем часть токена после первого “_“).

Оно будет следующим:

Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKI***D4Q%2F20190831%2Fus-east-1%2Fsts%2Faws4_request&X-Amz-Date=20190831T092243Z&X-Amz-Expires=0&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=ff2***f7f

В поле Amz-Credential=AKI***D4Q как раз передаётся ACCESS_KEY нашего пользователя из профиля AWS CLI arseniy (про профили поговорим совсем скоро в в AWS профили):

[simterm]

$ cat ~/.aws/credentials | grep -B1 -A2 AKI***D4Q
[arseniy]
aws_access_key_id = AKI***D4Q
aws_secret_access_key = q0I***jvj

[/simterm]

Итак:

  1. передаём ACCESS_KEY
  2. используя ACCESS_KEY – получаем ARN пользователя
  3. используя ARN, aws-iam-authenticator на EKS Control Plane проверяет – разрешён ли пользователю доступ к кластеру (см. AWS EKS aws-auth ConfigMap)

Проверим наш ключ:

[simterm]

$ aws --profile arseniy  iam list-access-keys --user-name arseniy
{
    "AccessKeyMetadata": [
        {
            "UserName": "arseniy",
            "AccessKeyId": "AKI***D4Q",
...

[/simterm]

AWS-аккаунт, в котором используется ключ:

[simterm]

$ aws --profile arseniy sts get-access-key-info --access-key-id AKI***D4Q
{
    "Account": "534***385"
}

[/simterm]

И ARN пользователя в этом аккаунте:

[simterm]

$ aws --profile arseniy iam get-user --user-name arseniy
{
    "User": {
        "Path": "/",
        "UserName": "arseniy",
        "UserId": "AID***JU6",
        "Arn": "arn:aws:iam::534***385:user/arseniy",
...

[/simterm]

Всё хорошо.

AWS CLI vs aws-iam-authenticator

Аналогично – вместо AWS CLI мы можем вызвать aws-iam-authenticator для получения токена, что бы совсем соответствовать картинке выше.

На Arch Linux устанавливаем его из AUR:

[simterm]

$ yaourt -S aws-iam-authenticator-bin

[/simterm]

И получаем токен:

[simterm]

$ aws-iam-authenticator token -i mobilebackend-dev-eks-0-cluster
{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1alpha1","spec":{},"status":{"expirationTimestamp":"2019-08-31T10:38:41Z","token":"k8s-aws-v1.aHR***ODU"}}

[/simterm]

Можно вручную изменить конфиг kubectl, и вызвать aws-iam-authenticator, т.е. вместо:

...
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - --region
      - us-east-2
      - eks
      - get-token
      - --cluster-name
      - mobilebackend-dev-eks-0-cluster
      command: aws
...

Зададим:

...
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
        - token
        - -i
        - mobilebackend-dev-eks-0-cluster
      command: aws-iam-authenticator
      env:
      - name: AWS_PROFILE
        value: arseniy
...

Проверяем доступ:

[simterm]

$ kubectl auth can-i get pods
yes

[/simterm]

Т.е. тут мы видим, что kubectl просто получает токен, используя один из доступных ему методов, заданных через ~/.kube/conf – либо используя AWS CLI (вызывая /usr/bin/aws), либо с помощью /usr/bin/aws-iam-authenticator.

AWS профили

Давайте рассмотрим ещё одну часть файла ~/.kube/config, а именно – env:

...
      command: aws
      env:
      - name: AWS_PROFILE
        value: arseniy

Тут ещё в переменную AWS_PROFILE задаётся и имя профиля AWS CLI, для которого требуется получить токен, см. AWS: именованные профили доступа.

Итак:

  1. kubectl считывает ~/.kube/config
    1. находит API Server URL
    2. находит команду для получения токена (command и args)
    3. проверяет профиль пользователя, для которого требуется получить токен
    4. обращается к AWS, получает токен
  2. обращается к API-серверу, передавая токен аутентификации

Проверяем.

Получаем токен:

[simterm]

$ export AWS_PROFILE=arseniy
$ aws-iam-authenticator token -i mobilebackend-dev-eks-0-cluster
{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1alpha1","spec":{},"status":{"expirationTimestamp":"2019-08-31T11:00:15Z","token":"k8s-aws-v1.aHR***Y2E"}}

[/simterm]

Проверяем его – получаем ARN пользователя:

[simterm]

$ aws-iam-authenticator token -verify -i mobilebackend-dev-eks-0-cluster -t $token&{ARN:arn:aws:iam::534***385:user/arseniy CanonicalARN:arn:aws:iam::534***385:user/arseniy AccountID:534***385 UserID:AID***JU6 SessionName:}

[/simterm]

Для удобства – сохраним его в переменную $token:

[simterm]

$ token="k8s-aws-v1.aHR***Y2E"

[/simterm]

И простым curl – пробуем подключиться к API-серверу:

[simterm]

$ curl -X GET https://715***834.sk1.us-east-2.eks.amazonaws.com/api --insecure --header "Authorization: Bearer $token"
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "ip-172-16-49-148.us-east-2.compute.internal:443"
    }
  ]
}

[/simterm]

Точно так же сам kubectl выполняет API-запросы к Kubernetes.

Кстати – все эти запросы можно посмотреть в CloudWatch Logs, если вы включили их при создании или в настройках кластера, например логи аутентификации – в логах, внезапно, authentificator-***:

heptio-authenticator-aws vs aws-iam-authenticator

Сначала удивился, почему в логих виден heptio-authenticator-aws, если в документации AWS говорится про aws-iam-authenticator? См. Managing Cluster Authentication.

Но всё оказалось просто: до версии 4.0 aws-iam-authenticator назывался heptio-authenticator-aws.

См. v0.3.0 и v0.4.0-alpha.1.

Хорошо – тут мы разобрались. А что происходит дальше?

AWS EKS aws-auth ConfigMap

Собственно, тут и начинается “магия” аутентификации – теперь aws-iam-authenticator должен:

  1. проверить у самого AWS IAM – есть ли запрошенный пользователь в системе, и имеет ли он права, т.е. выполнить его аутентифицакию
  2. передать его API-серверу Kubernetes, после чего тот выполнит авторизацию – проверит, имеет ли этот пользоваль право доступа к кластеру, и тут как раз используется aws-auth ConfigMap

Если вернуться к созданию кластера из поста Kubernetes: знакомство, часть 2 — создание кластера с AWS cloud-provider и AWS LoadBalancer –  мы создавали там ConfigMap который выглядел следующим образом:

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::534***385:role/mobilebackend-dev-eks-0-wn-stack-NodeInstanceRole-15NNFZK6WW4IG
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

Где в строке arn:aws:iam::534***385:role/mobilebackend-dev-eks-0-wn-stack-NodeInstanceRole-15NNFZK6WW4IG мы передаём ARN роли, которой разрешён доступ к кластеру. Группы обсудим в Авторизации, в следующей части.

Добавление пользователя к кластеру

И вернёмся к вопросу – почему другой IAM-пользователь получает сообщение “You must be logged in to the server (Unauthorized)“?

Собственно, ответ явно виден выше – потому что его нет в нашем ConfigMap.

Проверяем документацию  aws-iam-authenticator:

...
  # each mapUsers entry maps an IAM role to a static username and set of groups
  mapUsers:
...

Обновляем содержимое нашего ConfigMap – добавляем ARN пользоваля, его логин, и задаём ему группу system:masters:

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::534***385:role/mobilebackend-dev-eks-0-wn-stack-NodeInstanceRole-15NNFZK6WW4IG
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
  mapUsers: |
    - userarn: arn:aws:iam::534***385:user/yaroslav
      username: yaroslav
      groups:
        - system:masters

Применяем:

[simterm]

$ kubectl apply -f aws-auth-cm.yaml 
configmap/aws-auth configured

[/simterm]

Проверяем его на кластере – находим сам ConfigMap:

[simterm]

$ kubectl -n kube-system get cm
NAME                                 DATA   AGE
aws-auth                             2      2d
...

[/simterm]

И проверяем его содержимое:

[simterm]

$ kubectl -n kube-system describe cm aws-auth
Name:         aws-auth
Namespace:    kube-system
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"v1","data":{"mapRoles":"- rolearn: arn:aws:iam::534***385:role/mobilebackend-dev-eks-0-wn-stack-NodeInstanceRole-15NNFZK...

Data
====
mapRoles:
----
- rolearn: arn:aws:iam::534***385:role/mobilebackend-dev-eks-0-wn-stack-NodeInstanceRole-15NNFZK6WW4IG
  username: system:node:{{EC2PrivateDNSName}}
  groups:
    - system:bootstrappers
    - system:nodes

mapUsers:
----
- userarn: arn:aws:iam::534***385:user/yaroslav 
  username: yaroslav 
  groups: 
    - system:masters

Events:  <none>

[/simterm]

Готово – теперь arn:aws:iam::534***385:user/yaroslav может выполнять любые операции на сервере.

“root” aka Cluster creator

Последний нюанс во всей этой схеме, это факт того, что вы никак не можете увидеть создателя кластера, из-за чего, собственно, и возникло непонимание: пользователь arn:aws:iam::534***385:user/arseniy имел полный доступ к системе, а arn:aws:iam::534***385:user/yaroslav – нет.

Причём найти “создателя” пока удалось только в CloudTrail по API-вызову к AWS – CreateCluster :

Возникает она потому что:

The IAM identity that created the EKS cluster is automatically “hardwired” in the AWS IAM Authenticator. This means that this IAM identity is recognised and authenticated (and mapped to a user in the system:masters group) by the AWS IAM Authenticator without being listed in the aws-auth ConfigMap.

И при этом никакой возможности увидеть её где-то “внутри” самого EKS вроде как нет (но я написал в саппорт – может подскажут).

UPD 02.09.2019

Пришёл ответ от саппорта – всё-таки увидеть нельзя:

At this time, the IAM entity that creates the cluster becomes the first cluster administrator. This entity is passed to
the master nodes and is not visible from the `aws-auth` ConfigMap. This is similar to the root user for your AWS account
in that it has the system:masters permission.


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