К нам в апи приходят запросы от клиента (spa на react). Один из запросов - запрос на получение статистики. Статистика хранится в MySQL тут же на сервере и перед ее отдачей мы проверяем, авторизован ли пользователь и имеет ли он доступ к этой статистике.
По мере роста объемов мы решили перевести статистику на отдельный сервер и отдельный домен. Кроме того, подключили туда ClickHouse, чтобы эффективнее обрабатывать данные.
Теперь встал вопрос, как делать проверку авторизованности. Цепочка получается такая:
client -> api -> statistics
То есть с клиента приходит запрос в апи, апи проверяет права пользователя и дёргает сервер со статистикой.
Теперь нужно сделать так, чтобы сервер со статистикой отдавал данные только серверу апи, чтобы никто посторонний не смог получить данные.
Какие тут варианты? Вот что приходит в голову:
1. Белый список адресов. Сервер статистики проверяет с какого айпи к нему пришел запрос и отдает его только в случае совпадения. Мне не нравится этот способ, потому что у нас несколько серверов, в том числе и резервные. Если мы будем докупать/менять сервера, то список нужно держать в голове.
2. Передавать какой-то секретный ключ с сервера АПИ и проверять его на сервере статистики. Этот вариант мне кажется наиболее удобным, но в каком виде его хранить и как передавать?
Посоветуйте, пожалуйста.
Если не хочешь быть первым - не вставай в очередь!
2. Передавать какой-то секретный ключ с сервера АПИ и проверять его на сервере статистики. Этот вариант мне кажется наиболее удобным, но в каком виде его хранить и как передавать?
Видится мне, что это самый разумный вариант. Список адресов - не слишком надёжен и не особо логичен.
Как передавать - так же как и сам запрос, в виде параметра, заголовка или любой другой части запроса, которая в конечном итоге будет разбираться на сервере. Как хранить - мы обычно храним в ENV (переменных окружения) или конфигурационных файлах проекта.
WebDev, Да. По крайней мере я не вижу в этом методе никаких особых недостатков. На счёт "случайной" строки - я думаю в целом подойдёт любая строка, которую Вы сочтёте достаточно надёжной (по длине и энтропии). Главное, что бы обмен между серверами происходил по защищённым каналам (если это не локальная сеть), по SSH например или через HTTPS (и т.п.), что бы Ваш "пароль" не стал достоянием отдельно взятых заинтересованных лиц (гипотетически).
Use client certificate authentication, Luke! Только колхозить подписание запросов не нужно, как рекомендует камрад Кирилл Горелов, достаточно просто TLS наладить. Заодно получите шифрованный канал данных. Если нативно сервер этого не умеет, можно на фронт поставить nginx (у вас же там наверняка какой-нибудь REST или что-нибудь такое, да?), который прекрасно умеет в аутентификацию по клиентским сертификатам.
Итак, читаем дружно что такое OpenId и OAuth2. Потом читаем про существование Identity Server. Если хочется все самим то на вскидку Keycloak, если хочется сервис то Auth0. В догонку читаем про токены JWT. Если не нравятся предложенные варианты то ищем аналоги. Идея в том что если есть несколько мест аутентификации то надо выносить аутентификацию отдельно. Жду глупых или умных вопросов в комментариях)
athacker, это простое и масштабируемое решение, которое настраивается клац-клац) получаем стандартные протоколы безопасности и библиотеки на любой вкус и цвет + настройки. А вот то что ты заявляешь про достаточность TLS меня несколько смешит ибо это безопасность только на уровне обмена данными, но контроль доступа не обеспечивает никак. Ну и в догонку - они уже начинают разделять сервисы, а это процесс изменения архитектуры и появление SOA, а тут один путь)
athacker, генерировать на каждого пользователя свой сертификат и подписывать им запрос? Чушь. Запросы и так должны передаваться по SSL, а контент запроса сверху еще защищать шифрованием в целом бессмысленно. Там огромное число проблем, не говоря уже при том что при равной длине ключа симметричное шифрование безопаснее ассиметричного и служат они для разных целей. TLS это вообще - transport layer security. Идем в RFC, читаем спеки и прекращаем забивать гвозди микроскопом
Вы постановку задачи-то прочитайте сначала. А то неловко как-то получается -- выглядит так, как будто вы с голосами в собственной голове разговариваете.
Задача -- обеспечить возможность получения статистики только от серверов с API-шлюзами, не прибегая при этом к ограничению подключений на уровне IP-адресов. При чём тут пользователи вообще? Пользователями API-шлюз целиком занимается.
Также, пассаж про "симметричное/асимметричное" выдаёт ваше полное незнакомство с сутью TLS, и как там обеспечивается взаимная аутентификация и шифрование трафика. Стыдно, товарищ.
Задача -- обеспечить возможность получения статистики только от серверов с API-шлюзами
Что приведет очень быстро к bottleneck и slow train и в результате все-равно будет распределенная сеть микросервисов и аутентификацией пользователей независимо
Стыдно, товарищ.
Пишу что по памяти, но в голове просто закрепился данный факт. И я буду настаивать на том что асимметричное шифрование полезно для других задач. Подпись запроса это хорошо, но мы уже подписываем запрос по https (ssl) и сервер принимает на один хост только один сертификат, а это значит что для второго слоя мы должны подписывать тело запроса (которого может и не быть) и присылать какой-то идентификатор ключа.
athacker, я там был - давно и много раз) мы с разных сторон на проблему смотрим. Это хорошо работает server-to-server но я говорю о том что проблема вылезет все-равно на обращение с клиента и валидацию доступа пользователя. Просто прямо сейчас из вопроса и того что уже сделано ТС это не очевидно. Многие так делали и все переделывают по тому что это наращивает огромный архитектурный долг
athacker @inoise
Я не понимаю, зачем все это настраивать если:
1) Мне не нужна никакая авторизация и не понадобится, мне просто нужно отдавать данные только своим серверам.
2) Я не передаю никакие данные, поэтому мне не нужно ничего шифровать.
OAuth, JWT и OpenID - это про авторизацию. Мне ведь авторизация не нужна и не будет нужна.
В соседнем ответе предлагают шифровать открытым и закрытым ключом, но и этого не требуется, потому что я не передаю никаких данных, мне нечего шифровать.
Что приведет очень быстро к bottleneck и slow train и в результате все-равно будет распределенная сеть микросервисов и аутентификацией пользователей независимо
О чем тут речь? Почему это будет узким местом и причем тут аутентификация пользователей? Если вы говорите, что придется горизонтально масштабироваться, то как это повлияет на архитектуру? На всех серверах будет храниться один и тот же "ключ".
WebDev, ну во-первых, вы путаете аутентификацию и авторизацию. Это два разных процесса. То, что нужно конкретно вам в данном случае называется "аутентификация". Авторизация -- это про определение того, куда можно аутентифицированному пользователю ходить и что там делать.
Во-вторых, чтобы "просто отдавать данные только своим серверам", как раз и нужна аутентификация. Т. е. сервер статистики должен как-то уметь отличать "свои" сервера от чужих. Для этого "свои" сервера должны как-то себя обозначить, а сервер статистики должен это проверить по каким-то критериям.
В самом простом случае -- это белый список на файрволе, где сервер "предъявляет" свой source IP. Ну, по факту, отдельного никакого предъявления там нет, просто файрвол проверяет IP источника. Но в контексте аутентификации можно рассуждать и так.
Альтернативной схемой аутентификации, которая довольно просто прикручивается, является взаимная аутентификация по сертификатам. Т. е. при подключении клиента, сервер предъявляет свой сертификат, а клиент проверяет -- может он доверять этому сертификату (выписан ли он доверенным удостоверяющим центром), или нет. И затем сервер может запросить клиентский сертификат (в общем случае это опционально, но в вашем случае нужно настроить это обязательным). Тогда клиент должен предъявить свой сертификат, и сервер проверит его по своему списку УЦ, которым доверяет сам сервер. Если всё ОК, то идёт дальнейшее согласование сессии и соединение устанавливается, и вот уже там всё происходит. Именно так работает протокол TLS. Также в рамках этого протокола устанавливается шифрованый канал. Вы утверждаете, что шифрование вам не нужно. Ну ОК, ттолько получается, что кто угодно может подсмотреть идущий по открытому каналу трафик и потом прикинуться вашим сервером (допустим, вы как-то прикрутили парольную аутентификацию между API сервером и сервером статистики), и без проблем гонять запросы к вашему серверу статистики в обход серверов API.
не передаю никаких данных
Вы шутите, что ли? API сервер "дёргает сервер статистики" -- это что, не требует установки соединения, передачи запроса и получения ответа от сервера статистики? А что тогда "передача данных", по вашему?
athacker, Я неверно выразился.
TLS настроен, общение происходит через него.
В итоге у меня картина такая получилась:
- Обмен данными происходит поверх TLS.
- Я сгенерировал ключ (просто длинная случайная строка) и сохранил его на обоих серверах.
- Сервер api обращается за данными к серверу аналитики и передает ключ. Ну, например вот так:
На сервере аналитики я принимаю этот запрос, беру key, который пришел и сравниваю его с ключом, который лежит здесь локально. Если они равны, значит запрос пришел с моего сервера и я отдаю данные.
Есть какие-либо минусы в такой простой реализации?
UPD: Когда я написал, что шифрование мне не нужно, я имел ввиду этот ключ. Мне советовали его шифровать и подписывать, а не передавать в голом виде, но так как он идет по защищенному каналу, то в этом нет необходиости.
WebDev, если у вас TLS уже есть, то зачем вообще генерировать какие-то ключи? При настройке TLS у сервера по-любому уже должен быть серверный сертификат. Вам остаётся только в настройках сервера указать, что нужно требовать от клиента его клиентский сертификат, и сгенерировать сертификат для клиента, который он будет предъявлять при установке TLS-сессии.
А товарища выше не слушайте, он вообще не в теме, как работает TLS, хотя и утверждет, что читал стандарты.
С помощью подписей.
Есть открытый + закрытый ключ.
Закрытым ключом подписываем наши данные, отправляем серверу с открытым ключом.
На стороне api по открытым ключу ищем закрытый, то есть достоверный источник. Проверяем подпись. И если все ок, то пропускаем.
Есть класс на php и python, что бы можно было общаться в обе стороны, если разные языки.
Ваш вариант хорош, если нужно передавать какие-то данные.
Но ведь не нужно ничего передавать, мне просто нужно удостовериться, что запрос пришел именно с наших серверов.
Какая в этом случае разница, отправлю я зашифрованную и подписанную строку или просто сгенерирую длинную случайную строку? Данных то там нет внутри.
WebDev, точно таким же образом ты можешь сделать следующее:
1. передавать просто открытый ключ и по нему искать у список твоих серверов.
2. ты можешь просто эту сгенерированную строку подписать и проверять, по ней, что строка не просто сгенерирована, а еще и от достоверного источника.
Вообще, конечно, да, я не заметил что передавать ничего не надо. Тогда конечно такой вариант и не подойдет.