[Apache, Lua, Nginx, Safari] Как мы в ZeroTech подружили Apple Safari и клиентские сертификаты с websocket-ами
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Статья будет полезна тем, кто:
— знает, что такое Client Cert, и понимает для чего ему websocket-ы на мобильном Safari;
— хотел бы публиковать web-сервисы ограниченному кругу лиц или только себе;
— думает, что всё уже кем-то сделано, и хотел бы сделать мир немного удобнее и безопаснее.
История веб-сокетов началась примерно 8 лет назад. Ранее использовались методы вида долгих http-запросов (на самом деле ответов): браузер пользователя отправлял запрос на сервер и ждал, пока он ему что-то ответит, после ответа подключался вновь и ждал. Но потом появились веб-сокеты.
Несколько лет назад мы разработали собственную реализацию на чистом php, которая не умеет использовать запросы https, так как это канальный уровень. Не так давно практически все web-серверы научились проксировать запросы по https и поддерживать connection:upgrade.
Когда это случилось, веб-сокеты стали практически сервисом по умолчанию у SPA-приложений, ведь как удобно предоставлять пользователю контент по инициативе сервера (передать сообщение от другого пользователя или загрузить новую версию изображения, документа, презентации, которую сейчас кто-то ещё редактирует).
Хотя Сlient Сert появился уже довольно давно, он всё ещё остаётся мало поддерживаемым, так как создаёт массу проблем с попытками его обойти. И (возможно :slightly_smiling_face: ) поэтому IOS-браузеры (все, кроме Safari) не хотят его использовать и запрашивать у локального хранилища сертификатов. Сертификаты обладают массой преимуществ по сравнению с ключами login/pass или ssh или закрытием через firewall нужных портов. Но речь не об этом.
На IOS процедура установки сертификата довольно проста (не без специфики), но в общем делается по инструкциям, которых в сети очень много и которые доступны только для браузера Safari. К сожалению, Safari не умеет использовать Сlient Сert для веб-сокетов, но в интернете есть множество инструкций, как сделать такой сертификат, но на практике это недостижимо.
Чтобы разобраться в веб-сокетах, мы использовали следующий план: проблема/гипотеза/решение.
Проблема: отсутствует поддержка веб-сокетов при проксировании запросов к ресурсам, которые защищены клиентским сертификатом на мобильном браузере Safari для IOS и других приложений, которые включили у себя поддержку сертификатов.
Гипотезы:
1. Возможно настроить такое исключение для использования сертификатов (зная, что их не будет) к веб-сокетам внутренних/внешних проксируемых ресурсов.
2. Для веб-сокетов можно сделать уникальное безопасное и защищаемое соединение с помощью временных сессий, которые генерируются при обычном (не веб-сокет) запросе браузера.
3. Временные сессии можно реализовать с помощью одного proxy web-сервера (только встроенные модули и функции).
4. Временные сессии-токены уже были реализованы в качестве готовых модулей apache.
5. Временные сессии-токены можно реализовать, логически спроектировав структуру взаимодействий.
Видимое состояние после внедрения.
Цель работы: управление сервисами и инфраструктурой должно быть доступно с мобильного телефона на IOS без дополнительных программ (таких как VPN), унифицировано и безопасно.
Дополнительная цель: экономия времени и ресурсов/трафика телефона (некоторые сервисы без веб-сокетов генерируют лишние запросы) с ускорением отдачи контента на мобильном интернете.
Как проверить?
1. Открытие страниц:
— например, https://teamcity.yourdomain.com в мобильном браузере Safari (доступен также в десктопной версии) — вызывает успешное подключение к веб-сокетам.
— например, https://teamcity.yourdomain.com/admin/admin.html?item=diagnostics&tab=webS…— показывает ping/pong.
— например, https://rancher.yourdomain.com/p/c-84bnv:p-vkszd/workload/deployment:danidb:ph…-> viewlogs — показывает логи контейнера.
2. Или в консоли разработчика:
Проверка гипотез:
1. Возможно настроить такое исключение для использования сертификатов (зная, что их не будет) к веб-сокетам внутренних/внешних проксируемых ресурсов.
Тут было найдено 2 решения:
а) На уровне
<Location sock*> SSLVerifyClient optional </Location>
<Location /> SSLVerifyClient require </Location>
менять уровень доступа.
У данного метода возникли такие нюансы:
— Проверка сертификата происходит после запроса к проксируемому ресурсу, то есть post request handshake. Это означает, что прокси сначала нагрузит, а потом отсечёт запрос к защищаемому сервису. Это плохо, но не критично;
— В протоколе http2. Он ещё находится в draft-е, и производители браузеров не знают, как его реализовать #info about tls1.3 http2 post handshake (not working now) Implement RFC 8740 «Using TLS 1.3 with HTTP/2»;
— Непонятно, как унифицировать эту обработку.
б) На базовом уровне разрешить ssl без сертификата.
SSLVerifyClient require => SSLVerifyClient optional, но это снижает уровень защиты proxy-сервера, так как такое соединение будет обработано без сертификата. Однако можно далее запретить доступ к проксируемым сервисам такой директивой:
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteRule .? - [F]
ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"
Более подробная информация – в статье о ssl: Apache Server Client Certificate Authentication
Оба варианта были проверены, выбран вариант «б» за универсальность и совместимость с протоколом http2.
Для завершения проверки этой гипотезы потребовалось немало экспериментов с конфигурацией, были проверены конструкции:
if = require = rewrite
— Apache Core Features
— Expressions in Apache HTTP Server
Получилась следующая базовая конструкция:
SPL
SSLVerifyClient optional
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule .? - [F]
#ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"
#websocket for safari without cert auth
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
...
#замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола
SSLUserName SSl_PROTOCOL
</If>
</If>
С учётом существующей авторизации по владельцу сертификата, но при отсутствующем сертификате пришлось добавить несуществующего владельца сертификата в виде одной из доступных переменных SSl_PROTOCOL (вместо SSL_CLIENT_S_DN_CN), подробнее в документации:
Apache Module mod_ssl
2. Для веб-сокетов можно сделать уникальное безопасное и защищаемое соединение с помощью временных сессий, которые генерируются при обычном (не веб-сокет) запросе браузера.
Исходя из предыдущего опыта нужно добавить дополнительную секцию в конфигурацию, чтобы при обычном (не веб-сокет) запросе готовить временные токены для веб-сокет соединений.
#подготовка передача себе Сookie через пользовательский браузер
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
Header set Set-Cookie "websocket-allowed=true; path=/; Max-Age=100"
</If>
</If>
#проверка Cookie для установления веб-сокет соединения
<source lang="javascript">
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
#check for exists cookie
#get and check
SetEnvIf Cookie "websocket-allowed=(.*)" env-var-name=$1
#or rewrite rule
RewriteCond %{HTTP_COOKIE} !^.*mycookie.*$
#or if
<If "%{HTTP_COOKIE} =~ /(^|; )cookie-name\s*=\s*some-val(;|$)/ >
</If
</If>
</If>
Проверка показала, что это работает. Возможно через пользовательский браузер передавать cебе Cookie.
3. Временные сессии можно реализовать с помощью одного proxy web-сервера (только встроенные модули и функции).
Как мы выяснили ранее, у Apache довольно много core-функциональности, которая позволяет создавать условные конструкции. Однако нам нужны средства защиты нашей информации, пока она находится в пользовательском браузере, поэтому устанавливаем, что и для чего хранить, и какие встроенные функции будем задействовать:
— Нужен такой токен, который не поддаётся простому декодированию.
— Нужен такой токен, в котором зашито устаревание и возможность проверки устаревания на сервере.
— Нужен такой токен, который будет связан с владельцем сертификата.
Для этого нужна функция хеширования, соль и дата для устаревания токена. Исходя из документации Expressions in Apache HTTP Server у нас есть всё это из коробки sha1 и %{TIME}.
Получилась такая конструкция:
SPL
#нет сертификата, и обращение к websocket
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
SetEnvIf Cookie "zt-cert-sha1=([^;]+)" zt-cert-sha1=$1
SetEnvIf Cookie "zt-cert-uid=([^;]+)" zt-cert-uid=$1
SetEnvIf Cookie "zt-cert-date=([^;]+)" zt-cert-date=$1
#только так можно работать с переменными, полученными в env-ах в этот момент времени, более они нигде не доступны для функции хеширования (по отдельности можно, но не вместе, да и ещё с хешированием)
<RequireAll>
Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1}
Require expr %{env:zt-cert-sha1} =~ /^.{40}$/
</RequireAll>
</If>
</If>
#есть сертификат, запрашивается не websocket
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
SetEnvIf Cookie "zt-cert-sha1=([^;]+)" HAVE_zt-cert-sha1=$1
SetEnv zt_cert "path=/; HttpOnly;Secure;SameSite=Strict"
#Новые куки ставятся, если старых нет
Header add Set-Cookie "expr=zt-cert-sha1=%{sha1:salt1%{TIME}salt3%{SSL_CLIENT_S_DN_CN}salt2};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
Header add Set-Cookie "expr=zt-cert-uid=%{SSL_CLIENT_S_DN_CN};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
Header add Set-Cookie "expr=zt-cert-date=%{TIME};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
</If>
</If>
Цель достигнута, но есть проблемы с серверным устареванием (можно использовать Cookie годичной давности), а значит токены, хоть и безопасны для внутреннего использования, но небезопасны для промышленного (массового).
4. Временные сессии-токены уже были реализованы в качестве готовых модулей Аpache.
С предыдущей итерации осталась одна существенная проблема — невозможность контролировать устаревание токена.
Ищем готовый модуль, который это делает, по словам: apache token json two factor auth
— Client authentication using tokens based on JSON Web Tokens
— Apache Two-Factor (2FA) Authentication
— How to Add Two-Factor Authentication to Apache
— Bring two-factor authentication to your Apache instance with a simple module install
Да, готовые модули есть, но все привязаны к конкретным действиями и обладают артефактами в виде старта сессии и дополнительных Cookie. То есть не на время.
У нас ушло пять часов на поиск, который не дал конкретного результата.
5. Временные сессии-токены можно реализовать, логически спроектировав структуру взаимодействий.
Готовые модули слишком сложны, ведь нам нужно только пару функций.
При этом проблема с датой в том, что встроенные функции Apache не позволяют генерировать дату из будущего, а при проверке устаревания во встроенных функциях нет математического сложения/вычитания.
То есть нельзя написать:
(%{env:zt-cert-date} + 30) > %{DATE}
Можно сравнивать только два числа.
При поиске обхода проблемы Safari нашлась интересная статья: Securing HomeAssistant with client certificates (works with Safari/iOS)
В ней описан пример кода на Lua для Nginx, и который, как оказалось, очень повторяет логику той части конфигурации, которую мы уже ранее реализовали, за исключением использования hmac-метода расстановки соли для хеширования (такого в Apache не нашлось).
Стало понятно, что Lua — это язык, с понятной логикой, возможно сделать что-то простенькое и для Apache:
— LuaHookAccessChecker Directive
— UnsetEnv Directive
Изучив разницу с Nginx и Apache:
— modules_lua
— lua_load_resty_core
И доступные функции от производителя языка Lua:
22.1 – Date and Time
Найден способ задания переменных env в небольшом Lua-файле для того, чтобы установить дату из будущего для сверки с текущей.
Вот так выглядит простенький Lua-скрипт:
SPL
require 'apache2'
function handler(r)
local fmt = '%Y%m%d%H%M%S'
local timeout = 3600 -- 1 hour
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())
return apache2.OK
end
И вот так это всё работает в сумме, c оптимизацией числа Cookie и заменой токена при наступлении половинного времени до истечения старых Cookie
SPL
SSLVerifyClient optional
#LuaScope thread
#generate event variables zt-cert-date-next
LuaHookAccessChecker /usr/local/etc/apache24/sslincludes/websocket_token.lua handler early
#запрещаем без сертификата что-то ещё, кроме webscoket
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule .? - [F]
#ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"
#websocket for safari without certauth
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
SetEnvIf Cookie "zt-cert=([^,;]+),([^,;]+),[^,;]+,([^,;]+)" zt-cert-sha1=$1 zt-cert-date=$2 zt-cert-uid=$3
<RequireAll>
Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1}
Require expr %{env:zt-cert-sha1} =~ /^.{40}$/
Require expr %{env:zt-cert-date} -ge %{env:zt-cert-date-now}
</RequireAll>
#замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола
SSLUserName SSl_PROTOCOL
SSLOptions -FakeBasicAuth
</If>
</If>
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
SetEnvIf Cookie "zt-cert=([^,;]+),[^,;]+,([^,;]+)" HAVE_zt-cert-sha1=$1 HAVE_zt-cert-date-halfnow=$2
SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1
Define zt-cert "path=/;Max-Age=%{env:zt-cert-timeout};HttpOnly;Secure;SameSite=Strict"
Define dates_user "%{env:zt-cert-date-next},%{env:zt-cert-date-halfnext},%{SSL_CLIENT_S_DN_CN}"
Header set Set-Cookie "expr=zt-cert=%{sha1:salt1%{env:zt-cert-date-next}sal3%{SSL_CLIENT_S_DN_CN}salt2},${dates_user};${zt-cert}" env=!HAVE_zt-cert-sha1-found
</If>
</If>
SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1
работает,
а так работать не будет
SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge env('zt-cert-date-now') && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1
Потому что LuaHookAccessChecker будет активирован только после проверок доступа исходя из этой информации от Nginx.
Cсылка на источникизображения.
Ещё один момент.
В целом неважно, в какой последовательности в конфигурации Аpache (вероятно и Nginx) написаны директивы, так как в итоге всё будет отсортировано исходя из очерёдности прохождения запроса от пользователя, который соответствует схеме для отработки Lua-скриптов.
Завершение:
Видимое состояние после внедрения (цель):
Управление сервисами и инфраструктурой доступно с мобильного телефона на IOS без дополнительных программ (VPN), унифицировано и безопасно.
Цель достигнута, веб-сокеты работают и обладают не меньшим уровнем безопасности, чем сертификат.
===========
Источник:
habr.com
===========
Похожие новости:
- [Apache, DevOps, Nginx, Разработка веб-сайтов, Серверное администрирование] Расширенная настройка web сервера (Nginx + Apache2)
- [.NET, Apache, Машинное обучение, Microsoft Azure, Microsoft SQL Server] Critical Transcendence: .NET SDK and Apache Spark
- Релиз языка программирования Lua 5.4
- [JavaScript, Lua, PHP, Хакатоны] Эволюция real-time Web: примеры из практики (или с чем Lua справляется лучше JS)
- [Big Data] Побег от скуки — процессы ETL
- [Laravel, Nginx, PHP, Программирование, Разработка веб-сайтов] Деплой приложения на Laravel 7 на Ubuntu & Nginx
- [IT-инфраструктура, Open source, Настройка Linux, Разработка под Linux] Использование секретов Kubernetes в конфигурациях Kafka Connect
- [Apache, Big Data, Машинное обучение] Распределенное обучение XGBoost и параллельное прогнозирование с Apache Spark (перевод)
- [IT-компании, Nginx, Законодательство в IT] МВД прекратило уголовное дело о правах на Nginx за отсутствием состава преступления
- [Nginx, Микросервисы] Что такое Service Mesh? (перевод)
Теги для поиска: #_apache, #_lua, #_nginx, #_safari, #_websocket, #_apple_safari, #_lua, #_nginx, #_apache, #_blog_kompanii_zerotech (
Блог компании ZeroTech
), #_apache, #_lua, #_nginx, #_safari
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 03-Дек 22:50
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Статья будет полезна тем, кто: — знает, что такое Client Cert, и понимает для чего ему websocket-ы на мобильном Safari; — хотел бы публиковать web-сервисы ограниченному кругу лиц или только себе; — думает, что всё уже кем-то сделано, и хотел бы сделать мир немного удобнее и безопаснее. История веб-сокетов началась примерно 8 лет назад. Ранее использовались методы вида долгих http-запросов (на самом деле ответов): браузер пользователя отправлял запрос на сервер и ждал, пока он ему что-то ответит, после ответа подключался вновь и ждал. Но потом появились веб-сокеты. Несколько лет назад мы разработали собственную реализацию на чистом php, которая не умеет использовать запросы https, так как это канальный уровень. Не так давно практически все web-серверы научились проксировать запросы по https и поддерживать connection:upgrade. Когда это случилось, веб-сокеты стали практически сервисом по умолчанию у SPA-приложений, ведь как удобно предоставлять пользователю контент по инициативе сервера (передать сообщение от другого пользователя или загрузить новую версию изображения, документа, презентации, которую сейчас кто-то ещё редактирует). Хотя Сlient Сert появился уже довольно давно, он всё ещё остаётся мало поддерживаемым, так как создаёт массу проблем с попытками его обойти. И (возможно :slightly_smiling_face: ) поэтому IOS-браузеры (все, кроме Safari) не хотят его использовать и запрашивать у локального хранилища сертификатов. Сертификаты обладают массой преимуществ по сравнению с ключами login/pass или ssh или закрытием через firewall нужных портов. Но речь не об этом. На IOS процедура установки сертификата довольно проста (не без специфики), но в общем делается по инструкциям, которых в сети очень много и которые доступны только для браузера Safari. К сожалению, Safari не умеет использовать Сlient Сert для веб-сокетов, но в интернете есть множество инструкций, как сделать такой сертификат, но на практике это недостижимо. Чтобы разобраться в веб-сокетах, мы использовали следующий план: проблема/гипотеза/решение. Проблема: отсутствует поддержка веб-сокетов при проксировании запросов к ресурсам, которые защищены клиентским сертификатом на мобильном браузере Safari для IOS и других приложений, которые включили у себя поддержку сертификатов. Гипотезы: 1. Возможно настроить такое исключение для использования сертификатов (зная, что их не будет) к веб-сокетам внутренних/внешних проксируемых ресурсов. 2. Для веб-сокетов можно сделать уникальное безопасное и защищаемое соединение с помощью временных сессий, которые генерируются при обычном (не веб-сокет) запросе браузера. 3. Временные сессии можно реализовать с помощью одного proxy web-сервера (только встроенные модули и функции). 4. Временные сессии-токены уже были реализованы в качестве готовых модулей apache. 5. Временные сессии-токены можно реализовать, логически спроектировав структуру взаимодействий. Видимое состояние после внедрения. Цель работы: управление сервисами и инфраструктурой должно быть доступно с мобильного телефона на IOS без дополнительных программ (таких как VPN), унифицировано и безопасно. Дополнительная цель: экономия времени и ресурсов/трафика телефона (некоторые сервисы без веб-сокетов генерируют лишние запросы) с ускорением отдачи контента на мобильном интернете. Как проверить? 1. Открытие страниц: — например, https://teamcity.yourdomain.com в мобильном браузере Safari (доступен также в десктопной версии) — вызывает успешное подключение к веб-сокетам.
— например, https://teamcity.yourdomain.com/admin/admin.html?item=diagnostics&tab=webS…— показывает ping/pong. — например, https://rancher.yourdomain.com/p/c-84bnv:p-vkszd/workload/deployment:danidb:ph…-> viewlogs — показывает логи контейнера. 2. Или в консоли разработчика: Проверка гипотез: 1. Возможно настроить такое исключение для использования сертификатов (зная, что их не будет) к веб-сокетам внутренних/внешних проксируемых ресурсов. Тут было найдено 2 решения: а) На уровне <Location sock*> SSLVerifyClient optional </Location>
<Location /> SSLVerifyClient require </Location> менять уровень доступа. У данного метода возникли такие нюансы: — Проверка сертификата происходит после запроса к проксируемому ресурсу, то есть post request handshake. Это означает, что прокси сначала нагрузит, а потом отсечёт запрос к защищаемому сервису. Это плохо, но не критично; — В протоколе http2. Он ещё находится в draft-е, и производители браузеров не знают, как его реализовать #info about tls1.3 http2 post handshake (not working now) Implement RFC 8740 «Using TLS 1.3 with HTTP/2»; — Непонятно, как унифицировать эту обработку. б) На базовом уровне разрешить ssl без сертификата. SSLVerifyClient require => SSLVerifyClient optional, но это снижает уровень защиты proxy-сервера, так как такое соединение будет обработано без сертификата. Однако можно далее запретить доступ к проксируемым сервисам такой директивой: RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS RewriteRule .? - [F] ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site" Более подробная информация – в статье о ssl: Apache Server Client Certificate Authentication Оба варианта были проверены, выбран вариант «б» за универсальность и совместимость с протоколом http2. Для завершения проверки этой гипотезы потребовалось немало экспериментов с конфигурацией, были проверены конструкции: if = require = rewrite — Apache Core Features — Expressions in Apache HTTP Server Получилась следующая базовая конструкция:SPLSSLVerifyClient optional
RewriteEngine on RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS RewriteCond %{HTTP:Upgrade} !=websocket [NC] RewriteRule .? - [F] #ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site" #websocket for safari without cert auth <If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"> <If "%{HTTP:Upgrade} = 'websocket'"> ... #замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола SSLUserName SSl_PROTOCOL </If> </If> С учётом существующей авторизации по владельцу сертификата, но при отсутствующем сертификате пришлось добавить несуществующего владельца сертификата в виде одной из доступных переменных SSl_PROTOCOL (вместо SSL_CLIENT_S_DN_CN), подробнее в документации: Apache Module mod_ssl 2. Для веб-сокетов можно сделать уникальное безопасное и защищаемое соединение с помощью временных сессий, которые генерируются при обычном (не веб-сокет) запросе браузера. Исходя из предыдущего опыта нужно добавить дополнительную секцию в конфигурацию, чтобы при обычном (не веб-сокет) запросе готовить временные токены для веб-сокет соединений. #подготовка передача себе Сookie через пользовательский браузер
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'"> <If "%{HTTP:Upgrade} != 'websocket'"> Header set Set-Cookie "websocket-allowed=true; path=/; Max-Age=100" </If> </If> #проверка Cookie для установления веб-сокет соединения <source lang="javascript"> <If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"> <If "%{HTTP:Upgrade} = 'websocket'"> #check for exists cookie #get and check SetEnvIf Cookie "websocket-allowed=(.*)" env-var-name=$1 #or rewrite rule RewriteCond %{HTTP_COOKIE} !^.*mycookie.*$ #or if <If "%{HTTP_COOKIE} =~ /(^|; )cookie-name\s*=\s*some-val(;|$)/ > </If </If> </If> Проверка показала, что это работает. Возможно через пользовательский браузер передавать cебе Cookie. 3. Временные сессии можно реализовать с помощью одного proxy web-сервера (только встроенные модули и функции). Как мы выяснили ранее, у Apache довольно много core-функциональности, которая позволяет создавать условные конструкции. Однако нам нужны средства защиты нашей информации, пока она находится в пользовательском браузере, поэтому устанавливаем, что и для чего хранить, и какие встроенные функции будем задействовать: — Нужен такой токен, который не поддаётся простому декодированию. — Нужен такой токен, в котором зашито устаревание и возможность проверки устаревания на сервере. — Нужен такой токен, который будет связан с владельцем сертификата. Для этого нужна функция хеширования, соль и дата для устаревания токена. Исходя из документации Expressions in Apache HTTP Server у нас есть всё это из коробки sha1 и %{TIME}. Получилась такая конструкция:SPL#нет сертификата, и обращение к websocket
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"> <If "%{HTTP:Upgrade} = 'websocket'"> SetEnvIf Cookie "zt-cert-sha1=([^;]+)" zt-cert-sha1=$1 SetEnvIf Cookie "zt-cert-uid=([^;]+)" zt-cert-uid=$1 SetEnvIf Cookie "zt-cert-date=([^;]+)" zt-cert-date=$1 #только так можно работать с переменными, полученными в env-ах в этот момент времени, более они нигде не доступны для функции хеширования (по отдельности можно, но не вместе, да и ещё с хешированием) <RequireAll> Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1} Require expr %{env:zt-cert-sha1} =~ /^.{40}$/ </RequireAll> </If> </If> #есть сертификат, запрашивается не websocket <If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'"> <If "%{HTTP:Upgrade} != 'websocket'"> SetEnvIf Cookie "zt-cert-sha1=([^;]+)" HAVE_zt-cert-sha1=$1 SetEnv zt_cert "path=/; HttpOnly;Secure;SameSite=Strict" #Новые куки ставятся, если старых нет Header add Set-Cookie "expr=zt-cert-sha1=%{sha1:salt1%{TIME}salt3%{SSL_CLIENT_S_DN_CN}salt2};%{env:zt_cert}" env=!HAVE_zt-cert-sha1 Header add Set-Cookie "expr=zt-cert-uid=%{SSL_CLIENT_S_DN_CN};%{env:zt_cert}" env=!HAVE_zt-cert-sha1 Header add Set-Cookie "expr=zt-cert-date=%{TIME};%{env:zt_cert}" env=!HAVE_zt-cert-sha1 </If> </If> Цель достигнута, но есть проблемы с серверным устареванием (можно использовать Cookie годичной давности), а значит токены, хоть и безопасны для внутреннего использования, но небезопасны для промышленного (массового). 4. Временные сессии-токены уже были реализованы в качестве готовых модулей Аpache. С предыдущей итерации осталась одна существенная проблема — невозможность контролировать устаревание токена. Ищем готовый модуль, который это делает, по словам: apache token json two factor auth — Client authentication using tokens based on JSON Web Tokens — Apache Two-Factor (2FA) Authentication — How to Add Two-Factor Authentication to Apache — Bring two-factor authentication to your Apache instance with a simple module install Да, готовые модули есть, но все привязаны к конкретным действиями и обладают артефактами в виде старта сессии и дополнительных Cookie. То есть не на время. У нас ушло пять часов на поиск, который не дал конкретного результата. 5. Временные сессии-токены можно реализовать, логически спроектировав структуру взаимодействий. Готовые модули слишком сложны, ведь нам нужно только пару функций. При этом проблема с датой в том, что встроенные функции Apache не позволяют генерировать дату из будущего, а при проверке устаревания во встроенных функциях нет математического сложения/вычитания. То есть нельзя написать: (%{env:zt-cert-date} + 30) > %{DATE}
Можно сравнивать только два числа. При поиске обхода проблемы Safari нашлась интересная статья: Securing HomeAssistant with client certificates (works with Safari/iOS) В ней описан пример кода на Lua для Nginx, и который, как оказалось, очень повторяет логику той части конфигурации, которую мы уже ранее реализовали, за исключением использования hmac-метода расстановки соли для хеширования (такого в Apache не нашлось). Стало понятно, что Lua — это язык, с понятной логикой, возможно сделать что-то простенькое и для Apache: — LuaHookAccessChecker Directive — UnsetEnv Directive Изучив разницу с Nginx и Apache: — modules_lua — lua_load_resty_core И доступные функции от производителя языка Lua: 22.1 – Date and Time Найден способ задания переменных env в небольшом Lua-файле для того, чтобы установить дату из будущего для сверки с текущей. Вот так выглядит простенький Lua-скрипт:SPLrequire 'apache2'
function handler(r) local fmt = '%Y%m%d%H%M%S' local timeout = 3600 -- 1 hour 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()) return apache2.OK end И вот так это всё работает в сумме, c оптимизацией числа Cookie и заменой токена при наступлении половинного времени до истечения старых CookieSPLSSLVerifyClient optional
#LuaScope thread #generate event variables zt-cert-date-next LuaHookAccessChecker /usr/local/etc/apache24/sslincludes/websocket_token.lua handler early #запрещаем без сертификата что-то ещё, кроме webscoket RewriteEngine on RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS RewriteCond %{HTTP:Upgrade} !=websocket [NC] RewriteRule .? - [F] #ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site" #websocket for safari without certauth <If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"> <If "%{HTTP:Upgrade} = 'websocket'"> SetEnvIf Cookie "zt-cert=([^,;]+),([^,;]+),[^,;]+,([^,;]+)" zt-cert-sha1=$1 zt-cert-date=$2 zt-cert-uid=$3 <RequireAll> Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1} Require expr %{env:zt-cert-sha1} =~ /^.{40}$/ Require expr %{env:zt-cert-date} -ge %{env:zt-cert-date-now} </RequireAll> #замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола SSLUserName SSl_PROTOCOL SSLOptions -FakeBasicAuth </If> </If> <If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'"> <If "%{HTTP:Upgrade} != 'websocket'"> SetEnvIf Cookie "zt-cert=([^,;]+),[^,;]+,([^,;]+)" HAVE_zt-cert-sha1=$1 HAVE_zt-cert-date-halfnow=$2 SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1 Define zt-cert "path=/;Max-Age=%{env:zt-cert-timeout};HttpOnly;Secure;SameSite=Strict" Define dates_user "%{env:zt-cert-date-next},%{env:zt-cert-date-halfnext},%{SSL_CLIENT_S_DN_CN}" Header set Set-Cookie "expr=zt-cert=%{sha1:salt1%{env:zt-cert-date-next}sal3%{SSL_CLIENT_S_DN_CN}salt2},${dates_user};${zt-cert}" env=!HAVE_zt-cert-sha1-found </If> </If> SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1 работает, а так работать не будет SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge env('zt-cert-date-now') && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1 Потому что LuaHookAccessChecker будет активирован только после проверок доступа исходя из этой информации от Nginx. Cсылка на источникизображения. Ещё один момент. В целом неважно, в какой последовательности в конфигурации Аpache (вероятно и Nginx) написаны директивы, так как в итоге всё будет отсортировано исходя из очерёдности прохождения запроса от пользователя, который соответствует схеме для отработки Lua-скриптов. Завершение: Видимое состояние после внедрения (цель): Управление сервисами и инфраструктурой доступно с мобильного телефона на IOS без дополнительных программ (VPN), унифицировано и безопасно. Цель достигнута, веб-сокеты работают и обладают не меньшим уровнем безопасности, чем сертификат. =========== Источник: habr.com =========== Похожие новости:
Блог компании ZeroTech ), #_apache, #_lua, #_nginx, #_safari |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 03-Дек 22:50
Часовой пояс: UTC + 5