17 августа

Магия 2-х строк на Lua или как донести исходные заголовки HTTP Authorization header-авторизации до web-сервиcа

Блог компании ZeroTechNginxApacheLuaKubernetes
Статья будет полезна тем:

  • кому необходимо задействовать несколько видов авторизации в одном запросе к серверу;
  • кто хочет открывать сервисы мира Kubernetes/Docker в общий интернет, не задумываясь о способах защиты конкретного сервиса;
  • думает, что всё уже кем-то сделано, и хотел бы сделать мир немного удобнее и безопаснее.

Предисловие

Сервисы, которые становятся доступны через Kubernetes, имеют богатый набор способов авторизации. Один из наиболее модных – это заголовок Authorization: Bearer — это, например: JWT-авторизация (JSON Web Token) с передачей множества ключей, а следовательно, и значений, в одном заголовке. Встречаются и Basic-авторизации, например для Registry (хранилище образов Docker). Данная авторизация не использует Cookie и автоматически добавляется браузером (кроме Safari — там есть нюансы, которые мы пока не решаем) ко всем запросам к серверу.



Проблема 1:

Не получается авторизоваться через Firefox & Safari в интерфейсе Storage OS, показывается Loader, и на этом всё.

Мини-гипотеза:

Проблема в проксировании. Быстрая проверка показала результат: если авторизация без использования проксирования (универсальный безопасный доступ по сертификату), то всё работает. Так в чём же дело?

Проанализировав сетевой стек, мы поняли, что используется заголовок Authorization, однако ранее, в ходе настройки проксирования сервисов Rancher, было выяснено, что этот заголовок передаётся проксируемому сервису и содержит данные авторизации по сертификату, поэтому было решено его просто удалять по завершении процесса авторизации (FakeBasicAuth).

Проблема 2:

Во многих web-серверах авторизация по персональному сертификату реализована через эмуляцию Basic-авторизации (по сути, вмешательство в запрос пользователя), вероятно с целью уменьшения изменений в основном коде web-сервера. Называется такой способ FakeBasicAuth. После установления такого заголовка web-сервером затирается заголовок Authorization, который приходит от пользователя.

Гипотезы:

  1. Область видимости заголовка FakeBasicAuth поддаётся ещё большему ограничению, так что восстанавливается исходный заголовок для передачи на проксируемый ресурс таким образом, что будет передаваться только оригинальный заголовок, если он был.
  2. Область видимости заголовка Authorization может быть сконструирована так, что заголовок будет сохранён до активации механизма FakeBasicAuth и восстановлен после.

Видимое состояние — цель:

Storage OS авторизует, можно настраивать этот сервис, сохраняя единый подход к открытию доступности сервисов во внешний интернет.

Дополнительная цель:

Унифицированный, быстрый и безопасный доступ по http с сохранением функциональности всех возможных сервисов, работающих по стандарту http (например, REST API для мобильного приложения или Registry Docker).

Как проверить?

  • docker login registry-rancher.xxx.ru — используя ключи и логин/пароль.
  • storageos-rancher.xxx.ru/#/login — используя логин и пароль из конфигов secret rancher.xxx.ru/p/c-84bnv:p-qj9qm/secrets/kube-system:init-secr… (не работает в Safari).
  • registry-ui-rancher.xxx.ru — используя браузер и логин/пароль от Registry. Для внимательно читающих фишка: можно вместо стандартного docker login registry-rancher.xxx.ru использовать этот интерфейс — там встроено проксирование к Registry.

Проверка гипотез:

1. Исходя из предыдущего опыта, попробуем найти способ в интернете по таким запросам: apache authentification external basic via cert.

Нашлась более-менее адекватная статья про ldap. Вот таким образом:

RewriteEngine on
RewriteCond %{IS_SUBREQ} ^false$
RewriteCond %{LA-U:REMOTE_USER} (.+)
RewriteRule . - [E=RU:%1]
RequestHeader set REMOTE_USER %{RU}e

И далее прокинуть заголовок в дополнительных заголовках через конструкцию

RequestHeader add Authorization "expr=%{env:zt-auth-before}" "expr=%{env:zt-auth-before} =~/.{1,}/"

Но, к сожалению, эта конструкция не подразумевает создания идентичного заголовка, заголовку запроса пользователя, да и env формируется некорректно.

Поэтому способ на основе стандартных rewrite-ов оказался бесполезным и сложным.

2. Если не можем стандартно, значит, надо обратиться к lua, ранее видели, что есть блоки обработки запросов, которые выполняются до обработки сертификатов, снова посмотрим на блок-схему из статьи lua_load_resty_core и инструкцию module_lua_writinghooks c конструкцией early.

Получается, что мы можем задействовать тот же самый скрипт (Как мы в ZeroTech подружили Apple Safari и клиентские сертификаты с websocket-ами), чтобы сохранить заголовок Authorization до замены им на FakeBasicAuth.



LuaHookAccessChecker /usr/local/etc/apache24/sslincludes/websocket_token.lua handler early

В Lua это теперь выглядит так:


require 'apache2'

function handler(r)
        local fmt = '%Y%m%d%H%M%S'
        local timeout = 3600 -- 1 hour
        local auth = r.headers_in['Authorization']

        r.notes['zt-cert-timeout'] = timeout
        r.notes['zt-cert-date-next'] = os.date(fmt,os.time()+timeout)
        r.notes['zt-cert-date-halfnext'] = os.date(fmt,os.time()+ (timeout/2))
        r.notes['zt-cert-date-now'] = os.date(fmt,os.time())

        if auth ~= nil then
                r.notes['zt-auth-before'] = auth
        end

        return apache2.OK
end

Примечание:

Жирным шрифтом отмечены новые конструкции. И раз мы знаем, что env, полученный из Lua, доступен только для expr-выражений, добавляем конструкцию рядом с шифрованием zt-cert токена:

#исходящие Cookie пользователю


Header set Set-Cookie "expr=zt-cert=%{sha1:...

#передаём заголовки проксируемому сервису


RequestHeader add Authorization "expr=%{env:zt-auth-before}" "expr=%{env:zt-auth-before} =~/.{1,}/"

Доступность данных для передачи сервису проверяли через передачу данных обратно пользователю в браузер:

Header add Authorization "expr=%{env:zt-auth-before}" "expr=%{env:zt-auth-before} =~/.{1,}/"

Самое интересное здесь — это способ проверки наличия данных, чтобы не передавать заголовок проксируемому сервису, если он не пришёл снаружи от браузера пользователя. За это отвечает вторая часть конструкции:

"expr=%{env:zt-auth-before} =~/.{1,}/"

Завершение:

Готовых решений в интернете на текущий момент нет, потрачено около трех часов на поиск и попытки проверить вариации, так как не хотелось «изобретать велосипед».

Добавлено 5 строк, 3 из которых можно смело удалить. Как вы думаете, какие? — Пишите ваши варианты ответов в комментариях к статье.

Я не хотел писать об этом опыте, так как по-сути — это всего 2 строки и заголовок Authorization дойдёт до адресата, но решил всё же поделиться информацией, так как здесь используется хороший багаж знаний предыдущих исследований о сертификатах (речь об этой статье). К тому же вряд ли найдутся смельчаки написать что-то своё и настолько простое на неизвестном языке.

Теги:luakubernetesapachenginx
Хабы: Блог компании ZeroTech Nginx Apache Lua Kubernetes
+5
1,8k 20
Комментировать
Лучшие публикации за сутки