Русский
Русский
English
Статистика
Реклама

Openresty

Есть ли корпоративная жизнь на удаленке и как ее обеспечить интеграция внутренней системы аутентификации

30.04.2021 12:16:20 | Автор: admin

В digital-агентстве Convergent, где я работаю, в потоке множество проектов, и у каждого из них может быть собственная админка. Есть несколько окружений (дев, стейдж, лайв). А ещё есть разные внутрикорпоративные сервисы (как собственной разработки, так и сторонние вроде Redmine или Mattermost), которыми ежедневно пользуются сотрудники.

Наша команда всегда была распределённой между несколькими офисами, но с учётом событий последнего года все сотрудники перешли на удалёнку. Так мы столкнулись с необходимостью организовать всё многообразие внутренних и клиентских сервисов в единой системе.

В данной статье я хочу поделиться опытом создания собственной внутренней системы аутентификации на основе OpenResty, а также спецификации OAuth2. В качестве основного языка программирования мы используем PHP, а фреймворк Yii 2.

Суммирую необходимый функционал:

  • Единое место управления всеми доступами. Здесь происходит выдача и отзыв доступов в административные панели сайтов и списки доменов;

  • По умолчанию весь доступ запрещён, если не указано обратное (доступ к любым доменам контролируется с помощью OpenResty);

  • Аутентификация для сотрудников;

  • Аутентификация для клиентов.

Закрытый доступ к сайтам и инфраструктуре

Начну со схемы, как мы организовали инфраструктуру доступов.

Упрощенная схема взаимодействия между пользователями и серверамиУпрощенная схема взаимодействия между пользователями и серверами

Первое, что нужно было сделать, это закрыть доступ по умолчанию ко всем тестовым окружениям и инфраструктурным сервисам. Сотрудники в таком случае могут получить доступ ко всему путём добавления своего IP в вайтлист (об этом позже), а клиенты получают доступ точечно.

Фронт-контроллером в данном случае выступает OpenResty это модифицированная версия nginx, которая в т. ч. поддерживает из коробки язык Lua. На нём я написал прослойку, через которую проходят все HTTP(s)-запросы.

Вот так может выглядеть код скрипта аунтентификации (в упрощенном варианте):

auth.lua
function authenticationPrompt()    ngx.header.www_authenticate = 'Basic realm="Restricted by OpenResty"'    ngx.exit(401)endfunction hasAccessByIp()    local ip = ngx.var.remote_addr    local domain = ngx.var.host    local port = ngx.var.server_port    local res, err = httpc:request_uri(os.getenv("AUTH_API_URL") .. "/ip.php", {        method = "GET",        query = "ip=" .. ip .. '&domain=' .. domain .. '&port=' .. port,        headers = {          ["Content-Type"] = "application/x-www-form-urlencoded",        },        keepalive_timeout = 60000,        keepalive_pool = 10,        ssl_verify = false    })    if res ~= nil then        if (res.status == 200) then            session.data.domains[domain] = true            session:save()            return true        elseif (res.status == 403) then            return false        else            session:close()            ngx.say("Server error: " .. res.body)            ngx.exit(500)        end    else        session:close()        ngx.say("Server error: " .. err)        ngx.exit(500)    endendfunction hasAccessByLogin()    local header = ngx.var.http_authorization    local domain = ngx.var.host    local port = ngx.var.server_port    if (header ~= nil) then        header = ngx.decode_base64(header:sub(header:find(' ') + 1))        login, password = header:match("([^,]+):([^,]+)")        if login == nil then            login = ""        end        if password == nil then            password = ""        end        local res, err = httpc:request_uri(os.getenv("AUTH_API_URL") .. '/login.php', {            method = "POST",            body = "username=" .. login .. '&password=' .. password .. '&domain=' .. domain .. '&port=' .. port,            headers = {              ["Content-Type"] = "application/x-www-form-urlencoded",            },            keepalive_timeout = 60000,            keepalive_pool = 10,            ssl_verify = false        })        if res ~= nil then            if (res.status == 200) then                session.data.domains[domain] = true                session:save()                return true            elseif (res.status == 403) then                return false            else                session:close()                ngx.say("Server error: " .. res.body)                ngx.exit(500)            end        else            session:close()            ngx.say("Server error: " .. err)            ngx.exit(500)        end    else        return false    endendos = require("os")http = require "resty.http"httpc = http.new()session = require "resty.session".new()session:start()if (session.data.domains == nil) then    session.data.domains = {}endlocal domain = ngx.var.hostif session.data.domains[domain] == nil then    if (not hasAccessByIp() and not hasAccessByLogin()) then        session:close()        authenticationPrompt()    else        session:close()    endelse    session:close()end

Алгоритм работы скрипта довольно простой:

  • Поступает HTTP-запрос от пользователя;

  • OpenResty запускает скрипт auth.lua;

  • Скрипт определяет запрашиваемый домен и отправляет два запроса на внешний бэкенд;

  • Первый на проверку IP-адреса пользователя в базу;

  • Если IP отсутствует, выводит браузерное окно для ввода логина и пароля, отправляет второй запрос на проверку доступа;

  • В любой другой ситуации выводит окно Вход.

На GitHub я выложил рабочий пример, который можно быстро развернуть с помощью Docker.

Немного расскажу о том, как выглядит управление в нашей системе.

Отмечу, что IP-адреса попадают в базу при аутентификации пользователя. Это сделано специально для сотрудников. Такие адреса помечают как временные и доступные ограниченное время, после чего они удаляются.

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

Для клиентов же создаются простые доступы по паре логин-пароль. Такие доступы ограничиваются в пределах определенных доменных адресов.

Управление доступами по паролю и по IP-адресуУправление доступами по паролю и по IP-адресу

Управление базой сотрудников подтягивается в ID через синхронизацию с другой внутренней системой. Каждый новый сотрудник при добавлении в нашу CRM автоматически получает учётную запись в ID, а также все нужные письма с доступами и инструкциями для последующей настройки и работы.

Форма аутентификации для сотрудниковФорма аутентификации для сотрудников

Каждый день мы начинаем работу с данной аутентификации, таким образом добавляя свой IP-адрес в белый список для получения доступа к инфраструктуре.

Двухфакторная аутентификация

Для обеспечения двухфакторной аутентификации для сотрудников решено было добавить Google Authenticator. Такой механизм защиты позволяет больше обезопасить себя от утечки доступов. Для PHP есть готовая библиотека sonata-project/GoogleAuthenticator. Пример интеграции можно посмотреть здесь.

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

OAuth и OpenID

Третье, и не менее важное для нас, создание OAuth-сервера. За основу мы взяли модуль thephpleague/oauth2-server. Для Yii 2 готового решения не было, поэтому написали собственную имплементацию сервера. OAuth2 достаточно обширная тема, расписывать её работу в данной статье не буду. Библиотека имеет хорошую документацию. Также она поддерживает различные фреймворки, включая Laravel и Symfony.

Таким образом, любой сторонний сервис, который поддерживает кастомные OAuth2 конфигурации, достаточно просто подключается к нашей системе. Значимой фишкой такой интеграции стало подключение нашего ID к Mattermost. Последний в бесплатной версии поддерживает только аутентификацию с помощью GitLab, которую удалось эмулировать через наш сервис.

Также для всех наших проектов на Yii был разработан модуль для подключения ID. Это позволило вынести всё управление доступами в админпанели для сотрудников в централизованное место. Кстати, если интересно, я писал статью о модульном подходе, который мы применили в нашем digital-агентстве.

Заключение

Процесс адаптации компании под новую систему был относительно сложный, т. к. это потребовало доработки инфраструктуры и обучения работе с системой всех сотрудников компании. Просто так сказать вот у нас теперь ID, пользуйтесь им не получится, конечно, поэтому весь процесс миграции был очень тщательно задокументирован, а я выступал в качестве технической поддержки первые пару месяцев. Сейчас, спустя время, все процессы наладились, были внесены корректировки и добавлены новые фичи (например, автоматические письма новым сотрудникам с доступами и инструкциями).

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

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

Подробнее..

DDoS атаки на 7 уровень защита сайтов

09.05.2021 22:19:11 | Автор: admin
DDoS атаки на 7 уровень (на уровень приложения) наиболее простой способ привести в нерабочее состояние сайт и навредить бизнесу. В отличие от атак на другие уровни, когда для отказа сайта необходимо организовать мощный поток сетевого трафика, атаки на 7 уровень могут проходить без превышения обычного уровня сетевого трафика. Как это происходит, и как от этого можно защищаться я рассмотрю в этом сообщении.

Атаки 7 уровня на сайты включают атаки на уровень веб-сервера (nginx, apache и т.д.) и атаки на уровень сервера приложений (php-fpm, nodejs и т.д.), который, как правило, расположен за проксирующим сервером (nginx, apache и т.д.). С точки зрения сетевых протоколов, оба варианта являются атакой на уровень приложения. Но нам, с практической точки зрения, нужно разделить эти два случая. Веб-сервер (nginx, apache и т.д.), как правило, самостоятельно отдает статические файлы (картинки, стили, скрипты), а запросы на получение динамического контента проксирует на сервер приложений (php-fpm, nodejs и т.д.). Именно эти запросы становятся мишенью для атак, так как в отличие от запросов статики, серверы приложений при генерировании динамического контента требуют на несколько порядков больше ограниченных системных ресурсов, чем и пользуются атакующие.

Как ни банально звучит, чтобы защититься от атаки, ее нужно сначала выявить. На самом деле, к отказу сайта могут привести не только DDoS атаки, но и другие причины, связанные с ошибками разработчиков и системных администраторов. Для удобства анализа, необходимо добавить в формат логов nginx (сорри, варианта с apache у меня нет) параметр $request_time, и логировать запросы к серверу приложений отдельный файл:

      log_format timed '$remote_addr - $remote_user [$time_local] '            '$host:$server_port "$request" $status $body_bytes_sent '            '"$http_referer" "$http_user_agent" ($request_time s.)';      location /api/ {           proxy_pass http://127.0.0.1:3000;           proxy_http_version 1.1;           proxy_set_header Upgrade $http_upgrade;           proxy_set_header Connection 'upgrade';           proxy_set_header Host $host;           proxy_cache_bypass $http_upgrade;           proxy_set_header X-Real-IP $remote_addr;           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;           proxy_set_header X-NginX-Proxy true;           access_log /var/log/ngunx/application_access.log timed;       }


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

Выявив атаку можно, приступать к защите.

Очень часто, системные администраторы пытаются защитить сайт, ограничив количество запросов с одного IP-адреса. Для этого используют 1) Директиву limit_req_zone nginx (см. документацию), 2) fail2ban и 3) iptables. Безусловно, эти способы нужно использовать. Однако, такой способ защиты уже как 10-15 лет является малоэффективным. На это есть две причины:

1) Трафик, который генерирует сеть ботов при атаке на 7 уровень, по своему объему может быть меньше чем трафик обычного посетителя сайта, так как у обычного посетителя сайта на один тяжелый запрос к серверу приложений (php-fpm, nodejs и т.д.) приходится примерно 100 легких запросов на загрузку статических файлов, которые отдаются веб-сервером (nginx, apache и т.д.). От таких запросов iptables не защищает, так как может ограничивать трафик только по количественным показателям, и не учитывает разделение запросов на статику и динамику.

2) Вторая причина распределённость сети ботов (первая буква D в аббревиатуре DDoS). В атаке обычно принимает участие сеть из нескольких тысяч ботов. Они в состоянии делать запросы реже, чем обычный пользователь. Как правило, атакуя сайт, злоумышленник опытным путем вычисляет параметры limit_req_zone и fail2ban. И настраивает сеть ботов так, чтобы эта защита не срабатывала. Часто, системные администраторы начинают занижать эти параметры, отключая таким образом реальных клиентов, при этом без особого результата с точки зрения защиты от ботов.

Для успешной защиты сайта от DDoS, необходимо, чтобы на сервере были задействованы в комплексе все возможные средства защиты. В моем предыдущем сообщении на эту тему Защита от DDoS на уровне веб-сервера есть ссылки на материалы, как настроить iptables, и какие параметры ядра системы нужно привести в оптимальное значение (имеется в виду, прежде всего, количество открытых файлов и сокетов). Это является предпосылкой, необходимым, но не достаточным условием для защиты от ботов.

Кроме этого необходимо построить защиту, основанную на выявлении ботов. Все, что нужно для понимания механики выявления ботов, было подробно описано в исторической статье на Хабре Модуль nginx для борьбы с DDoS автора kyprizel, и реализована в библиотеке этого же автора testcookie-nginx-module

Это библиотека на С, и она продолжает развиваться небольшим сообществом авторов. Наверное, не все системные администраторы готовы на продакшин сервере прикомпилировать незнакомую библиотеку. Если же потребуется дополнительно внести изменения в работу библиотеки то это и вовсе выходит за рамки рядового системного администратора или разработчика. К счастью, в настоящее время появились новые возможности: скриптовый язык Lua, который может работать на сервере nginx. Есть две популярные сборки nginx со встроенным скриптовым движком Lua: openresty, разработка которого изначально споснировалась Taobao, затем Cloudfare, и nginx-extras, который включен в состав некоторых дистирибутивов Linux, например Ubuntu. Оба варианта используют одни и те же библиотеки, поэтому не имеет большой разницы, который из них использовать.

Защита от ботов может быть основана на определении способности веб-клиента: 1) выполнять код JavaScript, 2) делать редиректы и 3) устанавливать cookie. Из всех этих способов, выполнение кода JavaScript оказалось наименее перспективным, и от него я отказался, так как код JavaScript не выполняется, если контент загружается фоновыми (ajax) запросами, а повторная загрузка страницы средствами JavaScript искажает статистику переходов на сайт (так как теряется загловок Referer). Таким образом, остаются редиректы которые устанавливают cookie, значения котрых подчиняются логике, которая не может быть воспроизведена на клиенте, и не допускают на сайт клиентов без этих cookie.

В своей работе я основывался на библиотеке leeyiw/ngx_lua_anticc, которая в настоящее время не развивается, и я продолжил доработки в своем форке apapacy/ngx_lua_anticc, так как работа оригинальной библиотеки не во всем устраивала.

Для работы счетчиков запросов в библиотеке используются таблицы памяти, которые поддерживают удобные для наращивания значения счетчиков методы incr, и установку значений с TTL. Например, в приведенном ниже фрагменте кода наращивается значение счетчика запросов с одного IP-адреса, если у клиента не установлены cookie с определенным именем. Если счетчик еще не был инициализирован, он инициализируется значением 1 с TTL 60 секунд. После превышения количества запросов 256 (за 60 секунд), клиент на сайт не допускается:

local anticc = ngx.shared.nla_anticclocal remote_id = ngx.var.remote_addrif not cookies[config.cookie_name] then  local count, err = anticc:incr(remote_id, 1)  if not count then    anticc:set(remote_id, 1, 60)    count = 1   end   if count >= 256 then     if count == 256 then       ngx.log(ngx.WARN, "client banned by remote address")     end     ngx.exit(444)     return  endend


Не все боты являются вредными. Например, на сайт нужно пропускать поисковые боты и боты платежных систем, которые сообщают об изменении статусов платежей. Хорошо, если можно сформировать список всех IP-адресов, с которых могут приходить такие запросы. В этом случае можно сформировать белый список:

local whitelist = ngx.shared.nla_whitelistin_whitelist = whitelist:get(ngx.var.remote_addr)if in_whitelist then    returnend


Но не всегда это возможно. Одной из проблем является неопределенность с адресами ботов Google. Пропускать всех ботов подделывающихся под боты Google, равносильно снятию защиты с сайта. Поэтому, воспользуемся модулем resty.exec для выполнения команды host:

local exec = require 'resty.exec'if ngx.re.find(headers["User-Agent"],config.google_bots , "ioj") then    local prog = exec.new('/tmp/exec.sock')    prog.argv = { 'host', ngx.var.remote_addr }    local res, err = prog()    if res and ngx.re.find(res.stdout, "google") thenngx.log(ngx.WARN, "ip " .. ngx.var.remote_addr .. " from " .. res.stdout .. " added to whitelist")whitelist:add(ngx.var.remote_addr, true)        return    end    if res then        ngx.log(ngx.WARN, "ip " .. ngx.var.remote_addr .. " from " .. res.stdout .. "not added to whitelist")    else        ngx.log(ngx.WARN, "lua-resty-exec error: " .. err)    endend


Как показывает опыт, такая стратегия защиты позволяет защитить сайт от оределенного класса атак, которые часто используются для в целях недобросовестной конкуренции. Понимание же механизмов атак и способов защиты помогает сэкономить массу времени на безуспешные попытки защититься fail2ban-ом, а при использовании сторонней защиты (например от Cloudfare), более осознанно выбирать параметры защиты.

apapacy@gmail.com
9 мая 2021 года
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru