Сегодня я подробно разберу настройку аутентификации в Kubernetes с помощью Dex в связке с LDAP, а также покажу, как можно добавлять статических пользователей в Dex. 

В статье не буду останавливаться на основных принципах работы Dex, а сразу перейду к установке и настройке LDAP. Познакомиться с принципами работы Dex можно в этой статье.

Что будем делать:

  1. Установим OpenLDAP и настроим на нем поддержку STARTTLS. 
  2. Опишем структуру LDAP-каталога нашей организации.
  3. Включим поддержку OIDC (OpenID Connect) на kube-api-серверах.
  4. Получим SAN-сертификат для доменов, которые будет использовать Dex.
  5. Установим Dex и Dex-auth, где мы опишем LDAP-каталог и статических пользователей
  6. Сгенерируем kubeconfig нашего пользователя для работы с кластером.
  7. Настроим RBAC-авторизацию для групп и пользователей в кластере.

Итак, поехали.



Показывать буду на примере уже готового кластера Kubernetes с Helm версии 3 и Ingress, а также тремя доменными именами.

Устанавливаем и настраиваем OpenLDAP-сервер


В качестве LDAP будем использовать OpenLDAP на дистрибутиве ubuntu 18.04. 
Имя нашего сервера: openldap.dtln.cloud. 

  1. Подключаемся к серверу и приступаем к установке OpenLDAP. В процессе установки нам предложат установить пароль:

    sudo apt update 
    sudo apt install slapd ldap-utils
    

  2. Переконфигурируем OpenLDAP для нашего домена:

    sudo dpkg-reconfigure slapd 
    

  3. Выбираем No:


  4. Вводим доменное имя:


  5. Вводим имя организации:


  6. Повторяем ввод пароля:


  7. Включаем поддержку STARTTLS. Ставим необходимые пакеты:

    sudo apt install gnutls-bin ssl-cert
    

  8. Генерируем CA-ключ:

    sudo sh -c "certtool --generate-privkey > /etc/ssl/private/cakey.pem"
    

  9. Описываем нашу организацию в /etc/ssl/ca.info:

    cn = DTLN Company
    ca
    cert_signing_key
    

  10. Генерируем CA-сертификaт и ключ, который в дальнейшем будем использовать:

    sudo certtool --generate-self-signed --load-privkey /etc/ssl/private/cakey.pem --template /etc/ssl/ca.info --outfile /etc/ssl/certs/cacert.pem
    sudo certtool --generate-privkey --bits 1024 --outfile /etc/ssl/private/openldap_key.pem
    

  11. Создаем шаблон для сертификата /etc/ssl/openldap.info:

    organization = DTLN Company
    cn = openldap.dtln.cloud
    tls_www_server
    encryption_key
    signing_key
    expiration_days = 3650
    

  12. Генерируем сам сертификат:

    sudo certtool --generate-certificate --load-privkey /etc/ssl/private/openldap_key.pem --load-ca-certificate /etc/ssl/certs/cacert.pem --load-ca-privkey /etc/ssl/private/cakey.pem --template /etc/ssl/openldap.info --outfile /etc/ssl/certs/openldap.pem
    

  13. Настраиваем OpenLDAP для работы со STARTTLS. Вот что должно быть внутри файла certinfo.dif:

    dn: cn=config
    add: olcTLSCACertificateFile
    olcTLSCACertificateFile: /etc/ssl/certs/cacert.pem
    -
    add: olcTLSCertificateFile
    olcTLSCertificateFile: /etc/ssl/certs/openldap.pem
    -
    add: olcTLSCertificateKeyFile
    olcTLSCertificateKeyFile: /etc/ssl/private/openldap_key.pem
    

  14. Применяем созданный нами файл следующей командой:

    sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f certinfo.dif
    

  15. Назначаем права доступа на сертификат и перезапускаем наш сервис:

    sudo chgrp openldap /etc/ssl/private/openldap_key.pem
    sudo chmod 0640 /etc/ssl/private/openldap_key.pem
    sudo gpasswd -a openldap ssl-cert
    sudo systemctl restart slapd.service
    

  16. Чтобы проверить работу нашего сервиса с сертификатами, добавляем в /etc/ldap/ldap.conf строчку:

    TLS_CACERT /etc/ssl/certs/cacert.pem
    

  17. Проверяем работу через STARTTLS:

    ldapwhoami -H ldap://openldap.dtln.cloud -x -ZZ
    

    В случае успеха получаем:



    В случае проблемы проверяем еще раз пути до сертификатов и выполнение команд:


Описываем структуру LDAP-каталога


  1. Теперь опишем структуру каталога, создадим группы и двуx пользователей, которые будут ходить в Kubernetes. Создаем файл content.ldif:

    dn: ou=People,dc=openldap,dc=dtln,dc=cloud
    objectClass: organizationalUnit
    ou: People
    dn: cn=jane,ou=People,dc=openldap,dc=dtln,dc=cloud
    objectClass: person
    objectClass: inetOrgPerson
    sn: doe
    cn: jane
    mail: janedoe@openldap.dtln.cloud
    userpassword: foo_password
    
    dn: cn=john,ou=People,dc=openldap,dc=dtln,dc=cloud
    objectClass: person
    objectClass: inetOrgPerson
    sn: doe
    cn: john
    mail: johndoe@openldap.dtln.cloud
    userpassword: bar_password
    
    # Group definitions.
    
    dn: ou=Groups,dc=openldap,dc=dtln,dc=cloud
    objectClass: organizationalUnit
    ou: Groups
    
    dn: cn=admins,ou=Groups,dc=openldap,dc=dtln,dc=cloud
    objectClass: groupOfNames
    cn: admins
    member: cn=jane,ou=People,dc=openldap,dc=dtln,dc=cloud
    
    dn: cn=developers,ou=Groups,dc=openldap,dc=dtln,dc=cloud
    objectClass: groupOfNames
    cn: developers
    member: cn=jane,ou=People,dc=openldap,dc=dtln,dc=cloud
    member: cn=john,ou=People,dc=openldap,dc=dtln,dc=cloud
    

  2. Разворачиваем описанную структуру командой:

    ldapadd -x -D cn=admin,dc=openldap,dc=dtln,dc=cloud -W -f content.ldif
    

  3. Удостоверимся в наличии наших пользователей в каталоге:

    ldapwhoami -vvv -h openldap.dtln.cloud -p 389 -D cn=john,ou=People,dc=openldap,dc=dtln,dc=cloud -x -w bar_password -ZZ
    ldapwhoami -vvv -h openldap.dtln.cloud -p 389 -D cn=jane,ou=People,dc=openldap,dc=dtln,dc=cloud -x -w foo_password -ZZ
    

Подключаем поддержку OpenID Connect


Переходим к настройке кластера Kubernetes. 

Домен dex.ash.dtln.cloud используем для обращения Dex к API-серверу, а login.ash.dtln.cloud – для получения доступа к кластеру.

Мы разворачивали кластер без установщиков kubeadm или kops, так что OIDC-конфигурацию можно сразу добавлять в systemd-юнит. В остальных случаях настройку лучше сделать с помощью этих утилит.

  1. Редактируем юнит /etc/systemd/system/kube-apiserver.service и добавляем параметры запуска в секцию ExecStart:

    --oidc-client-id=dex-k8s-authenticator --oidc-groups-claim=groups --oidc-username-claim=email --oidc-issuer-url=https://dex.ash.dtln.cloud/ 
  2. Рестартуем наши API, проверяем, что они поднялись:

    sudo systemctl daemon-reload
    sudo systemctl restart kube-apiserver
    sudo systemctl status kube-apiserver
    

Создаем мультидоменный сертификат


Теперь переходим в кластер Kubernetes. 

  1. Ставим cert-manager, используя Helm: 

    helm repo add jetstack https://charts.jetstack.io
    helm repo update
    helm install cert-manager --namespace kube-system jetstack/cert-manager --version v0.13.0
    

  2. Описываем запрос на получение SAN-сертификата для наших доменов в cert.yml:

    ---
    apiVersion: cert-manager.io/v1alpha2
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-dex
    spec:
      acme:
        email: kubernetes@dataline.ru
        server: https://acme-v02.api.letsencrypt.org/directory
        privateKeySecretRef:
          name: letsencrypt-key-dex
        solvers:
        - http01:
            ingress:
              class: nginx
    ---
    apiVersion: cert-manager.io/v1alpha2
    kind: Certificate
    metadata:
      name: auth-dex
      namespace: kube-system
    spec:
      secretName: cert-auth-dex
      issuerRef:
        kind: ClusterIssuer
        name: letsencrypt-dex
      commonName: dex.ash.dtln.cloud
      dnsNames:
      - dex.ash.dtln.cloud
      - login.ash.dtln.cloud
    

  3. Выполняем команду:

    kubectl apply -f cert.yaml
    

  4. Теперь смотрим состояние нашего запроса на получение сертификата следующими командами: 

    kubectl get certificates --all-namespaces
    kubectl get challenges --all-namespaces
    

  5. Ждем подтверждения, процесс может занять некоторое время:



Устанавливаем Dex


Для Dex нам понадобятся ca.crt, ca.key c мастер-сервера. Обычно они лежат в каталоге /etc/kubernetes/pki/
Также нужен сгенерированный нами раннее CA-сертификат с OpenLDAP, расположенный по пути /etc/ssl/certs/cacert.pem

  1. Скачиваем исходники dex-authenticator и переходим в каталог:

    git clone git@github.com:mintel/dex-k8s-authenticator.git
    cd dex-k8s-authenticator/
    

  2. Готовим конфиг для Dex-auth, вставляем содержимое ранее скопированного ca.crt, важно соблюдать отступы:

    ---
    global:
      deployEnv: prod
    dexK8sAuthenticator:
      clusters:
        - name: 'ash.dtln.cloud'
          short_description: "k8s cluster"
          description: "Kubernetes cluster"
          issuer: https://dex.ash.dtln.cloud/
          k8s_master_uri: https://kubernetes.dtln.cloud:6443 # url or ip
          client_id: dex-k8s-authenticator
          static_context_name: false
          client_secret: acDEgDEcIg7RX0U7A9hlW2pGGraHDuMAZ4qFEKg2fUHHxr8
          redirect_uri: https://login.ash.dtln.cloud/callback
          k8s_ca_pem: |
            -----BEGIN CERTIFICATE-----
            # Вставляем содержимое ca.crt
            -----END CERTIFICATE-----
    ingress:
      enabled: true
      annotations:
        kubernetes.io/ingress.class: nginx
        kubernetes.io/tls-acme: "true"
      path: /
      hosts:
        - login.ash.dtln.cloud
      tls:
        - secretName: cert-auth-dex
          hosts:
            - login.ash.dtln.cloud
    
    

  3. Переводим содержимое cacert.pem в base64, чтобы добавить его в конфиг:

    cacert.pem | base64
    

  4. Готовим конфиг для Dex, также важно соблюдать отступы. По желанию добавляем статических пользователей в секцию staticPasswords и генерируем им пароль в формате bcrypt:

    ---
    global:
      deployEnv: prod
    tls:
      certificate: |-
        -----BEGIN CERTIFICATE-----
        #содержимое ca.crt
        -----END CERTIFICATE-----
      key: |-
        -----BEGIN RSA PRIVATE KEY-----
        #содержимое ca.key
        -----END RSA PRIVATE KEY-----
    ingress:
      enabled: true
      annotations:
        kubernetes.io/ingress.class: nginx
        kubernetes.io/tls-acme: "true"
      path: /
      hosts:
        - dex.ash.dtln.cloud
      tls:
        - secretName: cert-auth-dex
          hosts:
            - dex.ash.dtln.cloud
    serviceAccount:
      create: true
      name: dex-auth-sa
    config:
      issuer: https://dex.ash.dtln.cloud/
      storage:
        type: kubernetes
        config:
          inCluster: true
      web:
        http: 0.0.0.0:5556
      frontend:
        theme: "coreos"
        issuer: "kube-dtln"
        issuerUrl: "https://login.ash.dtln.cloud"
      expiry:
        signingKeys: "6h"
        idTokens: "24h"
      logger:
        level: debug
        format: json
      oauth2:
        responseTypes: ["code", "token", "id_token"]
        skipApprovalScreen: true
    
      connectors:
      - type: ldap
        id: ldap
        name: LDAP
        config:
          insecureNoSSL: false
          insecureSkipVerify: false
          startTLS: true # Включаем безопасное соединение
          rootCAData: |-
            #Содержимое cacert.pem в base64 
            #соблюдаем отступы
         
          host: openldap.dtln.cloud:389
          usernamePrompt: Email Address
          userSearch:
            baseDN: ou=People,dc=openldap,dc=dtln,dc=cloud
            filter: "(objectClass=person)"
            username: mail
            idAttr: DN
            emailAttr: mail
            nameAttr: cn
          groupSearch:
            baseDN: ou=Groups,dc=openldap,dc=dtln,dc=cloud
            filter: "(objectClass=groupOfNames)"
    
            userMatchers:
            - userAttr: DN
              groupAttr: member
    
            nameAttr: cn
    
      staticClients:
      - id: dex-k8s-authenticator
        name: Kubernetes Dev Cluster
        secret: 'acDEgDEcIg7RX0U7A9hlW2pGGraHDuMAZ4qFEKg2fUHHxr8'
        redirectURIs:
          - https://login.ash.dtln.cloud/callback
    
      enablePasswordDB: True
    
      staticPasswords:
        # Создаем статического пользователя с паролем в base64
      - email: "admin@dtln.cloud"
        # bcrypt hash of the string "password"
        hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
        username: "admin"
        userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
    
    

  5. Теперь ставим dex и dex-auth-чарты из текущего каталога с нашими конфигами:

    helm install dex --namespace kube-system --values dex.yaml charts/dex
    helm install dex-auth --namespace kube-system --values dex-auth.yml charts/dex-k8s-authenticator
    


Генерируем kubeconfig


  1. Ждем, когда поднимутся pod’ы. Смотрим их логи и идем по адресу login.ash.dtln.cloud. Логинимся под созданной нами учеткой, для авторизации мы использовали mail-формат логина. 

    Для static-записи выбираем вход через mail:


  2. После успешной аутентификации нас перенаправляют на страницу с инструкцией Dex-auth. C ее помощью мы генерируем kubeconfig-файл. В дальнейшем с ним мы будем подключаться к нашему кластеру:


  3. Теперь попытаемся получить доступ к кластеру и получаем ошибку. Все верно, наш пользователь не имеет прав, так что переходим к настройке RBAC.



Настраиваем RBAC-авторизацию


  1. Для примера привяжем статического пользователя к системной роли cluster-admin, а пользователей группы developers – к роли view, позволяющей только просматривать ресурсы. Тогда содержимое файла crb.yml должно быть таким:

    ---
    apiVersion: rbac.authorization.k8s.io/v1beta1
    kind: ClusterRoleBinding
    metadata:
      name: dex-admin
      namespace: kube-system
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin
    subjects:
    - kind: User
      name: "admin@dtln.cloud"
    
    ---
    apiVersion: rbac.authorization.k8s.io/v1beta1
    kind: ClusterRoleBinding
    metadata:
      name: dex-developers
      namespace: kube-system
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: view
    subjects:
    - kind: Group
      name: "developers"
    

  2. Переключаемся на основной контекст и применяем созданный yaml-файл на кластер:

    kubectl config set-context default
    kubectl apply -f crb.yml
    

  3. Смотрим доступные контексты и переключаемся обратно на нашего пользователя:

    kubectl config get-contexts
    kubectl config set-context johndoe-ash.dtln.cloud
    

На этом настройка Dex в связке с OpenLDAP закончена.

Пара советов напоследок:

  • Если возникают проблемы, первым делом нужно проверить форматирование yaml-файлов и обратить внимание на отступы. 
  • Слэши в адресах должны соответствовать примерам. 
  • Не забудьте посмотреть логи pod’ов Dex, Dex-auth, а также журналы юнитов kube-api на мастерах.