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.


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