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


На этот раз будут рассмотрены две темы, условно связанные одной темой: доступом пользователей к dev-окружению.

1. Как мы закрываем dev-контуры от лишних пользователей?


Перед нами часто стоит задача закрыть за basic auth или за белым списком весь dev-контур (десятки/сотни приложений), дабы туда не могли попасть поисковые боты или просто очень сторонние люди.

Обычно для ограничения доступов каждому Ingress и приложению надо создавать отдельные секреты basic auth. Управлять ими очень проблематично, когда набирается с десяток приложений. Поэтому мы организовали централизованное управление доступами.

Для этого был создан nginx с конфигурацией такого типа:

location / {
  satisfy any;

  auth_basic "Authentication or whitelist!";
  auth_basic_user_file /etc/nginx/htpasswd/htpasswd;

  allow 10.0.0.0/8;
  allow 175.28.12.2/32;
  deny  all;

  try_files FAKE_NON_EXISTENT @return200;
}

location @return200 {
  return 200 Ok;
}

Далее в Ingress'ах приложений мы просто добавляем аннотацию:

ingress.kubernetes.io/auth-url: "http://dev-auth.dev-auth-infra.svc.cluster.local"

Таким образом, при обращении к приложению запрос уходит в сервис dev-auth, который проверяет, введен ли корректный basic auth или же клиент входит в whitelist. Если одно из условий выполнено, запрос подтверждается и проходит в приложение.



В случае использования такого сервиса достаточно одного репозитория, в котором хранится список всех доступов и через который мы можем удобно конфигурировать свой «единый центр авторизации». Его распространение на новые приложения производится элементарным добавлением аннотации.

2. Как мы предоставляем доступ к приложениям внутри Kubernetes в dev-окружении?


… будь то Redis, RabbitMQ, PostgreSQL или любимый PHP-разработчиками Xdebug.

Очень часто, переводя приложения в Kubernetes, для обеспечения лучшей безопасности нам приходится закрывать доступ снаружи и вовсе. И тогда разработчики, которые привыкли «ходить своей IDE» в базу или в Xdebug, испытывают серьёзные трудности.

Для решения этой проблемы мы используем VPN прямо в кластере Kubernetes. Общая схема выглядит таким образом, что при подключении к VPN-серверу, запущенному в K8s, в конфигурационном файле OpenVPN мы push'им адрес DNS-сервера, который тоже живёт в K8s. OpenVPN конфигурирует VPN таким образом, что при запросе ресурса внутри Kubernetes он сначала попадает в DNS-сервер Kubernetes — например, за адресом сервиса redis.production.svc.cluster.local. DNS в Kubernetes резолвит его в адрес 10.244.1.15 и запросы на этот IP-адрес идут через OpenVPN прямо в кластер Kubernetes.

За время эксплуатации этого решения мы успели его неоднократно расширить. В частности:

  1. Так как мы не нашли простой и адекватной (для нашего случая) админки для учёта доступов к VPN, пришлось создать свой простой интерфейс — ведь официальный чарт предусматривает только вариант с запуском консольных команд для выпуска сертификатов.

    Получившаяся админка (см. также на Docker Hub) выглядит очень аскетично:


    Можно заводить новых юзеров или отзывать старые сертификаты:


    Также можно посмотреть конфиг для данного клиента:

  2. Мы добавили авторизацию в VPN'е на основе юзеров в GitLab, где проверяется пароль и активен ли юзер в GitLab. Это для случаев, когда клиенту хочется управлять юзерами, которые могут подключиться к dev VPN только на основе GitLab'а, и без использования дополнительных админок — в некотором смысле получается «SSO для бедных». За основу брали уже упомянутый готовый чарт.

    Для этого мы написали Python-скрипт, который при подключении юзера к OpenVPN, используя логин и пароль, сравнивает хэш в базе GitLab и проверяет его статус (активен ли он).



    Вот сам скрипт:

    #!/usr/bin/env python3
    
    # pip3 install psycopg2-binary bcrypt
    
    import bcrypt
    import sys
    import os
    import psycopg2
    import yaml
    
    with open("/etc/openvpn/setup/config.yaml", 'r') as ymlfile:
        cfg = yaml.load(ymlfile)
    
    def get_user_info(username=''):
        try:
            connect_str = "dbname=%s user=%s host=%s password=%s" % (cfg['db'], cfg['user'], cfg['host'], cfg['pass'])
            # use our connection values to establish a connection
            conn = psycopg2.connect(connect_str)
            # create a psycopg2 cursor that can execute queries
            cursor = conn.cursor()
            # create a new table with a single column called "name"
            cursor.execute("""SELECT encrypted_password,state FROM users WHERE username='%s';""" % username)
            # run a SELECT statement - no data in there, but we can try it
            rows = cursor.fetchall()
            print(rows)
            return(rows[0])
        except Exception as e:
            print("Uh oh, can't connect. Invalid dbname, user or password?")
            print(e)
    
    def check_user_auth():
        username = os.environ['username']
        password = bytes(os.environ['password'], 'utf-8')
        # hashed = bcrypt.hashpw(password, bcrypt.gensalt())
    
        user_info = get_user_info(username=username)
        user_encrypted_password = bytes(user_info[0], 'utf-8')
        user_state = True if user_info[1] == 'active' else False
    
        if bcrypt.checkpw(password, user_encrypted_password) and user_state:
            print("It matches!")
            sys.exit(0)
        else:
            print("It does not match :(")
            sys.exit(1)
    
    def main():
        check_user_auth()
    
    if __name__ == '__main__':
        main()

    А в конфиге OpenVPN просто указываем следующее:

    auth-user-pass-verify /etc/openvpn/auth-user.py via-env
    script-security 3
    client-cert-not-required


    Таким образом, если у клиента увольнялся сотрудник, его просто деактивировали в GitLab, после чего он не сможет подключиться и к VPN'у.

Вместо заключения


В продолжении цикла статей с практическими рецептами компании «Флант» по эксплуатации Kubernetes я раскрою такие темы, как выделение отдельных узлов под конкретные задачи (зачем и как?) и настройка под большие нагрузки служб вроде php-fpm/gunicorn, запущенных в контейнерах. Подписывайтесь на наш блог, чтобы не пропускать обновления!

P.S.


Другое из цикла K8s tips & tricks:


Читайте также в нашем блоге:

Комментарии (4)


  1. qwertyRu
    26.10.2018 14:29

    Для этого был создан nginx с конфигурацией такого типа:
    правильно ли я понял, что этот nginx и имеет dns имя dev-auth.dev-auth-infra.svc.cluster.local и к которому идут за авторизацией?


    1. Wimbo Автор
      26.10.2018 14:34

      Да.


  1. Stamm
    27.10.2018 16:29

    А вы как-то даёте доступ разработчикам к выполнению exec внутри подов?


    1. Wimbo Автор
      28.10.2018 11:09

      Чаще всего хватает доступов к Kubernetes Dashboard с авторизацией в gitlab.
      Иногда даем доступ с помощью kubectl (конфиг подключения) с юзером, которому доступны только те действия, что определили с помощью RBAC.