- Создал(а) Неизвестный пользователь (pavel.shevchenko), редактировал(а) Воронин Андрей Сергеевич июл 15, 2025

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

Основная задача прокси в том, что он должен исходя из своей конфигурации позволять обращаться к сервисам как с токеном авторизации, так и без него (для возможности открыть страницу авторизации). Будет закрыт прямой доступ ко всем существующим микросервисам и все запросы извне будут проходить через proxy-gateway. После обсуждения с Сергеем Жемотелем принято решение что закрытие всех микросервисов будет произведено путем удаления их ingress, в итоге в кубере будет единственный ingress - proxy-gateway.
Микросервис proxy-gateway разрабатывается на SpringFramework с использованием spring-cloud-gateway. Это известный и хорошо работающий reverse proxy сервис. Обсуждалось также использовать service discovery (eureka), убрать все ingress из кубера, настроить все сервисы на регистрацию в service discovery, тогда балансировкой будет полноценно заниматься zuul proxy, но необходимости в этом нет. Поэтому будем работать без service discovery, балансировка и circuit breaker останется на плечах Service кубера.

Полезные для нас возможности spring-cloud-gateway:

1. proxy- gateway в в этом случае будет написан на java и не будет необходимости писать новую логику для получения ролей токена, сейчас у нас есть библиотека от систематики - secure-configuration, которая всем этим занимается и имеет возможность подключения к spring приложению, в нее уже внесены доработки для поддержки webflux (требуется в spring-cloud-gateway).
2. мы можем строить таблицу маршрутизации по любым доступным правилам - хост, path, хедеры, контент запроса. Так можно будет держать один proxy-gateway который будет работать например и с uat и с prod и с dev окружением, исходя из хоста запроса можно направлять траффик на нужный стенд.
3. имеется возможность проксировать ws соединения, что будет необходимо для работы push уведомлениями в браузере.

Схема работы:

Ingress будет иметь только proxy-gateway, а все наши текущие сервисы будут доступны только через Service, таким образом можно будет убрать у них авторизацию т.к. доступ к ним будет возможен только через proxy-gateway который и будет проверять токены и роли пользователей. Внутри сети микросервисы должны работать через Service.

proxy- gateway требуется информация, по которой он может определять может ли он одобрить запрос к конкретному ендпоинту с той ролью, которая пришла в токене от клиента в виде "/path" - ["роли"]. Таблица маршрутизации хранится в базе Postgres - enpoints_access_info. Proxy-gateway получает содержимое таблицы маршрутов при запуске и далее каждые 10 минут (в текущей реализации пока так).

При таком подходе прокси будет иметь на руках информацию о доступах к ендпоинтам по ролям, далее нужно реализовать механизм актуализации этих данных, например при появлении новых ендпоинтов или при создании нового сервиса необходимо сразу же доставить эту информацию до прокси. И здесь проблема в человеческом факторе - сложно будет избежать опечаток или того что кто-то вообще забудет добавить эту информацию в базу. Поэтому сделано следующее: разработана библиотека pgs-proxy-library, которая содержит аннотацию по аналогии с аннотацией Secured будет принимать набор ролей в параметре, этой аннотацией необходимо будет разметить все ендпоинты сервиса, к которым нужен доступ из прокси, при старте приложения библиотека собирает всю информацию и отправляет ее в enpoints-access-info-service который будет сохранять ее в базе enpoints_access_info. Таким образом имеется два варианта заполнения таблицы маршрутизации:

1. с помощью библиотеки pgs-proxy-library (micronaut 3, java 17). Полностью автоматический способ.
2. в ручную заполнять таблицу маршрутизации.

Формат таблицы access_info:

имя поля | формат | описание | пример
--- | --- | --- | ---
имя поля | формат | описание | пример
id | uuid | primary key | aefe7255-6111-4365-886a-e84d805d7b05
app_name | varchar(255) | Имя приложения, должно быть равно имени его Service кубера | mz-history-2
pkg | varchar(500) | Абсолютный путь к java классу с контроллером, поле для информации, не участвует в логике. | sx.microservices.mz.history.controller.RequestController
method | varchar(255) | метод запроса - GET, POST и т.п. | GET
ingress_path | varchar(500) | Базовый путь к приложению, то же самое что и сейчас у него в пути ingress. | /mz/history
path | varchar(500) | Путь к ендпоинту внутри приложения, например "/requests/example". Поддерживается wildcard, пример - "/request/*" - будет принимать любое значение на месте * Еще пример "/request/*/*" | /request/example
rules | jsonb | Json с полями perms и roles, где perms - массив permissions, roles - массив ролей. Можно указать либо что-то одно либо ничего вообще. | {"perms": null, "roles": null} - означает что для доступа достаточно лишь валидного токена, без разницы какие у него роли.
manual | boolean | Поле для определения в ручную заполнен этот маршрут запросом в базу или автоматически сервисом enpoints-access-info-service | false
modified | timestamp | Дата редактирования | 1682606850000
modified_by | varchar(255) | Источник редактирования | Manual или AccessInfoService

## Принцип работы

При запуске proxy-gateway (далее - прокси) загружает таблицу маршрутизации из postgres (далее он ее актуализирует каждые 10 минут) и кладет к себе в кеш. При поступлении запроса срабатывает фильтр авторизации - из хедера Authorization либо из Cookie берется токен авторизации и производится проверка токена и извлечение его ролей из sso-service - это все стандартный механизм из secure-configuration, который сейчас используется на ПГС во всех сервисах где есть авторизация, затем он ищет среди ingress_path подходящий сервис для проксирования запроса, далее он определяет по path и method какие необходимы роли для доступа и если пришедший токен удовлетворяет условиям обращения к ендпоинту - проксирует запрос, иначе - выдает ошибку 401. Если не найден подходящий маршрут - возвращается ошибка 404. В прокси предусмотрены конфиги для безусловного доступа к ендпоинтам в не зависимости от наличия токена в запросе - whitelist paths, это было сделано строго для dev и uat площадок, сейчас там содержатся пути к swagger-ui и его составляющим, на проде не рекомендуется делать также. Для прокси дополнен параметр trustedIssuers - в него добавлено поле internalUrl - внутренний адрес sso-service (http://sso-service) для того, чтобы не ходить в sso (за проверкой токена) через внешнюю сеть (по факту через самого себя).

Параметры конфигурации
?

`proxy_gateway:` `` `# для прода указать "prod"` `` `spring_profile:` `dev` `` `microws_uri:` `http``:``//microws` `# внутренний путь к microws, будет нужен если будем прятать microws за прокси` `` `sso_service_uri:` `http``:``//sso-service` `# внутренний адрес sso-service, нужен для проксирования запросов к sso-service`
`` `no_roles_access_allowed:` `true` `# если true - то позволять обращаться к ендпоинтам у которых в таблице маршрутизации не заполнено поле rules - по факту это доступ к ендпоинту только с валидным токеном без проверки какие в нем есть роли`
`` `# для прода white_list_paths оставить пустым, этот параметр позволяет заходить по перечисленным в нем путям без авторизации`
`` `white_list_paths:` `/swagger-ui``,``/api``,``/res/swagger-ui-bundle.js``,``/res/swagger-ui-standalone-preset.js``,``/res/swagger-ui.css``,``/res/favicon-32x32.png``,``/res/favicon-16x16.png``,``/swagger/swagger.yml``,``/readiness``,``/liveness``,``/health`
`` `max_request_size:` `100MB`

Библиотека pgs-proxy-library на старте приложения находит все контроллеры с аннотацией PgsAccessRules, принимает из нее роли и permissions, формирует запись вида Method, Path, rules и отправляет через кафку в сервис endpoints-access-info, который сохраняет полную информацию о маршрутах в базу postgres актуализируя записи (если для этого сервиса записи уже есть - он их пересоздаст, таким образом поддерживается удаление и обновление ендпоинтов в таблице маршрутизации), при этом если у существующей записи стоит признак manual = true - то запись не будет обновлена либо удалена, это сделано для того, чтобы иметь возможность в ручную изменять записи и чтобы они после этого не перезаписывались автоматически.

## Этапы реализации:

## Примеры скриптов в таблицу access_info БД endpoints-access-info

Примеры указаны без учёта требования отдела DBA по оформлению скриптов для релиза.

Полностью одинаковых (дублей) записей в таблице access_info делать не нужно.

Добавление новой записи:

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

Скрипт добавления записи
?

`INSERT INTO access_info(app_name``,``pkg``,``method``,``ingress_path``,``path``,``rules``,``manual) VALUES` `(``'ervu-application-gateway'``,``'Manual'``,``'Get'``,``'/service/ervu-application-gateway'``,``'/applicationInfo/getWithActive/*'``,``'{"perms": [], "roles": []}'``,``'true'``)`

Также есть примеры в комментарии к задачам Невозможно найти сервер Jira для этого макроса. Причиной может быть конфигурация ссылки на приложение. и Невозможно найти сервер Jira для этого макроса. Причиной может быть конфигурация ссылки на приложение.

где

app_name - имя приложения, должен быть равен строго имени сущности Service кубера, по нему будет строится путь к сервису, например если app_name = cnsi-service, то путь будет начинаться с http://cnsi-service

pkg - имя класса контроллера, необязательно, делалось для отладок, сейчас особо не используется
method - тип rest метода (get, post, path, put ...) регистронезависимый

ingress_path - внешний путь к сервису, сюда нужно указать то, что было указано в ingress сервиса, например /service/cnsi/service

path - путь к ендпоинту, относительно ingress, например если app_name = cnsi-service, ingress_path = /service/cnsi/service, path = /classifiers/reload - то полный путь будет сформирован так: http://cnsi-service/service/cnsi/service/classifiers/reload

rules - jsonb с правами доступа, внутри объекта должны лежать roles и perms, где roles - требуемые для доступа роли, perms - требуемые permissions. Необязательное поле, если не заполнено - то для доступа к ендпоинту потребуется только валидный токен

manual - если эта запись в таблицу добавляется в ручную - значение этого поля должно быть строго равно true

Примечение:
Изначально обсуждалось что мы не будем использовать permissions, только roles. Желательно этого придерживаться, в текущих записях все таки где-то используем permissions.

Если есть возможность внедрить в сервис библиотеку pgs-proxy-library (http://nexus.gosuslugi.local/#nexus-search;quick~pgs-proxy-library) - то лучше использовать ее, она сама инициирует заполнение таблицы путей (через сообщения кафки в сервис endpoints-access-info) и будет их актуализировать. Основная версия для java17 и micronaut3, есть версии для java11 и micronaut2. Для работы библиотеки необходимо:

1. Обеспечить права на запись в топик pgs.proxy.endpoints.access.info

2. В application.yml добавить:
pgs-proxy:
enabled: true # на стендах пгс прокси не используется, поэтому там нужно выставить false
ingress-path: /service/ervu-object-diff-calc # сюда нужно указать базовый путь к сервису, если был ingress - взять путь из него
packages: rtl.pgs.ervu.objectdiffcalc # путь в корневому package приложения
kafka:
endpoints-access-info-topic: pgs.proxy.endpoints.access.info # имя топика, в который нужно отправлять инфу об эндпоинтах сервиса, не менять

3. Убрать ingress из хелмов

4. Все методы, к которым необходим внешний доступ, обозначить аннотацией @PgsAccessRules, в параметрах этой аннотации указать roles и perms, либо не указывать (если нужен доступ просто по валидному токену)

После добавления записи в таблицу (либо после деплоя сервиса с включенной библиотекой pgs-proxy-library) сервис прокси подхватит изменения в течении 10 минут.

Обновление записи в таблице:

Обновление по id, задание прав доступа:

Скрипт обновления записи
?

`UPDATE access_info` `SET rules =` `'{"roles": ["Администратор облака"], "perms": "read_user"}'` `WHERE id =` `2000000000001`

Удаление записи в таблице:

Для удаления лучше всего использовать id чтобы не удалить лишнего

Скрипт удаления записи
?

`DELETE` `FROM access_info` `WHERE id = <ID>`

Фильтровать данные таблицы Создать сводную таблицу Создать диаграмму
Configure buttons visibility
Нравится Станьте первыми кому понравится это

- Нет меток
- Редактировать метки

Написать комментарий...

Добавить комментарий

Обзор
Инструменты контента
{"serverDuration": 94, "requestCorrelationId": "989a346cc2bd1fca"}
search
recentlyviewed
attachments
weblink
advanced
image-effects
image-attributes

- Абзац

- Жирный
- Курсив
- Подчеркнутый
- Cредство выбора цвета
  Дополнительные цвета
- Форматирование

- Маркированный список
- Нумерованный список

- Список задач

- Убрать отступ
- Сделать отступ

- Выровнить по левому краю
- Выровнить по центру
- Выровнить по правому краю

- Разметка страницы

- Ссылка

- Таблица

- Вставить

- Разметка страницы

- Отменить
- Восстановить

- Найти/Заменить

- Справка по быстрым ссылкам на клавиатуре

Подключение...
1 / 1
proxy-gateway v1 [1]