OIDC
Основные концепции
Аутентификация с помощью протокола OpenID Connect (далее OIDC) реализована с помощью утилиты oauth2-proxy. Общая схема аутентификации представлена на рисунке ниже:
Пояснения к картинке указаны в таблице.
шаг | Описание |
---|---|
1. Запрос ресурса | Если запрос включен в список запросов без аутентификации (см skip_auth_routes), то он перенаправляется к серверу Svacer (Входа на сервер Svacer при этом не происходит. Будут доступны только те ресурсы, которые не требуют аутентификации). Если запроса нет в списке, то переходим к шагу 2 |
2. Проверка необходимости аутентификации OAuth2 | Утилита oAuth2-proxy, на основе имеющихся в куках данных, определяет необходимость перенаправления пользователя на сервер OAuth2. Если аутентификация необходима, то по по URL, который был указан в параметре oidc_issuer_url, прокси запросит точки входа в соответствии с протоколом OIDC. После получения нужной точки входа, пользователь будет перенеаправлен на нее (шаг 3), при этом в параметре redirect этого запроса будет указано значение параметра конфигурации прокси redirect_url. Если аутентификация не нужна (уже есть действительный токен от OIDC сервера), то запрос перенаправляется на сервер Svacer (с токеном доступа (Access Token) от сервера OAuth2 в заголовке X-Forwarded-Access-Token) - шаг 1* на рисунке |
3. Аутентификация пользователя на OAuth2 сервере | Если в куках сервера OAuth2 есть данные об активной сессии аутентификации, то пользователь будет перенаправлен обратно на прокси сервер (по указанному в параметре запроса URL). При этом, в параметрах URL будет передан Authorization Code для дальнейшего использования клиентом OAuth2 (в терминологии OAuth2 клиентом является oauth2-proxy). Если активной сессии нет, то будет отображена форма для логина и пароля пользователя. После успешного входа, OAuth2 сервер запомнит сессию пользователя, сформирует Authorization Code и перенаправит пользователя обратно на прокси, передав в URL значение Authorization Code |
4. Получение AccessToken | Прокси осуществляет получение Access Token от сервера OAuth2, используя полученный Authorization Code и точку входа сервера OAuth2 для получения токенов, которую он получил ранее на шаге 2. |
5. Отправка запроса к Svacer | Первоначальный запрос (из шага 1), отправляется серверу Svacer. При этом в заголовки этого запроса добавляется Access Token от OAuth сервера, а также другие специфичные для oauth2-proxy заголовки. Состав дополнительной информации, передаваемой в заголовках, определяется конфигурацией oauth2-proxy |
6. Выпуск токена Svacer | Svacer определяет наличие заголовка X-Forwarded-Access-Token в запросе. Если конфигурация сервера подразумевает использование OIDC протокола, то полученный токен проверяется и на его основе создается токен Svacer, после чего выполняется вход в систему и создается пользователь Svacer (если еще не создан), соответствующий пользователю, указанному в токене доступа от OAuth2 сервера |
Конфигурация Svacer для поддержки протокола OIDC
Для конфигурации svacer необходимо использовать секцию auth стандартного конфигурационного файла svacer, указываемого через опцию --config при запуске svacer.
svacer-server run --config config.yaml
Пример секции auth конфигурационного файла представлен ниже
<source lang="yaml">auth:
oidc: enabled: true debug: true oauth_proxy: enabled: true clock: 1 sign_in: "http://zeus.intra.ispras.ru:12800/realms/svacer/protocol/openid-connect/token" sign_out: "http://hadokku.intra.ispras.ru:4180/oauth2/sign_out?rd=/oauth2/sign_in" tokens: - claims: subject: preferred_username role: svacer_role first_name: given_name second_name: second_name last_name: family_name email: email sign: - key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvR/zIW/acyWFVSuSD/8iDM3epguHljKZkaB4ITojRwisXNcn6S0ArlI8v69qV1tBFi+RH3Ys4L5Pj+xDFlemlml6wPp5X0tcD2Gax51KHqQcFjViBGVSqJUdts/bkvfiIfZLkurybkhR0ALToWvbNyD7> name: rsa
</source> В секции используются следующие поля:
Название | Описание |
---|---|
enabled | Использовать OIDC провайдер. Значение false отключает данный функционал |
debug | Выводить в логи сервера дополнительные отладочные сообщения. Полезно при первичной настройке, в дальнейшем желательно установить в значение false |
oauth_proxy.enabled | Использовать утилиту oauth2-proxy в качестве механизма поддержки протокола OIDC. На текущий момент это единственная реализация поддержки OIDC протокола |
oauth_proxy.clock | Допускаемая погрешность расхождения времени между сервером аутентификации и клиентом. Указывается значение в минутах |
oauth_proxy.sign_in | Точка входа сервера OAuth2 для получения токена. Используется в Svacer только для работы cli команд и сервисной учетной записи. При некорректном значении работа Svacer клиента с сервером Svacer через OIDC будет невозможна. В случае правильной настройки других параметров, в GUI пользователи работать смогут. |
oauth_proxy.sign_out | Ссылка, которая будет использована при выходе пользователя из системы (logout в GUI). Более подробно см. Выход пользователя из системы |
oauth_proxy.tokens | Массив объектов (token), описывающих структуру JWT токена OIDC сервера и данные, необходимые для проверки выпускаемого им токена |
token.claims | Описывает структуру токена. Поля этой сущности определяют свойства создаваемого внутреннего пользователя Svacer и его права |
token.claims.subject | Название поля из Claims токена, значение которого будет использовано для заполнения свойства Логин у созданного пользователя Svacer |
token.claims.first_name | Название поля из Claims токена, значение которого будет использовано для заполнения свойства Имя у созданного пользователя Svacer |
token.claims.second_name | Название поля из Claims токена, значение которого будет использовано для заполнения свойства Отчество у созданного пользователя Svacer |
token.claims.last_name | Название поля из Claims токена, значение которого будет использовано для заполнения свойства Фамилия у созданного пользователя Svacer |
token.claims.email | Название поля из Claims токена, значение которого будет использовано для заполнения свойства E-Mail у созданного пользователя Svacer |
token.claims.role | Название поля из Claims токена, значение которого будет использовано для добавления роли пользователю Svacer. Значение должно быть именем уже существующей роли или значением "admin" для добавления роли администратора. Автоматическое назначение роли происходит только при первом создании пользователя |
token.sign | Содержит данные, необходимые для проверки подписи JWT токена |
token.sign.key | Открытый ключ, используемый OIDC сервером для проверки токена. Формат: BASE64(ASN1.DER) |
token.sign.name | Имя алгоритма ЭЦП. Поддерживаются: RSA, ECDSA |
Конфигурация OAuth2-proxy
Конфигурация oauth2-proxy может выполнятся через параметры cli, через указание параметров в конфигурационном файле, а также через переменные окружения. Названия параметров, передаваемых в cli, в конфигурационном файле и через cli находятся в следующем соответствии друг с другом (на примере параметра client_secret):
КОнфигурационный файл | CLI | Переменная окружения |
---|---|---|
client_secret | --client-secret | OAUTH2_PROXY_CLIENT_SECRET |
Для нормальной работы желательно наличие DNS имен для хостов (OAuth2-proxy использует Cookies для хранения там информации о сессии аутентификации пользователя а также для хранения csrf токена) . Ниже приводится пример конфигурации oauth2-proxy для подключения svacer сервера к OAuth2 серверу KeyCloak
standard_logging = true standard_logging_format = "[{{.Timestamp}}] [{{.File}}] {{.Message}}" auth_logging = true auth_logging_format = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}" pass_user_headers = true pass_host_header = true email_domains = [ "*" ] client_id = "svacer-auth-proxy" client_secret = "q2rNKGofxpw7FzmZDstSIyNNyzkW1BsW" pass_access_token = true cookie_name = "_oauth2_proxy2" cookie_secret = "2-W9DDrnlfBEK75F3zGpszuwTSqwBcUFQm6OQa3dHgU=" cookie_expire = "11m" cookie_refresh = "10m" cookie_secure = false provider = "keycloak-oidc" show_debug_on_error = true cookie_csrf_per_request = true cookie_csrf_expire = "36h" skip_auth_routes = ["GET=/static/.*","/api/public/*","GET=/api/auth_settings"]
В файле выше указаны не все требуемые параметры для нормальной работы oauth2-proxy. Ниже приведены наиболее важные параметры конфигурационного файла oauth2-proxy с пояснениями
Название | Описание |
---|---|
client_id | Название клиента (в терминах OAuth2), от имени которого пользователь будет аутентифицироваться на сервере OAuth |
client_secret | Секрет клиента (в терминах OAuth2) |
pass_access_token | Передавать токен доступа от Oauth2 сервера в Svacer (через заголовки). Данный токен будет использован сервером Svacer для идентификации и аутентификации пользователя, а также для получения первоначальной информации о нем |
provider | Имя провайдера OAuth2 |
cookie_csrf_per_request | Возможность отправки нескольких запросов одновременно |
skip_auth_routes | Указывается список запросов, которые не требуют аутентификации. Для корректной работы Svacer необходимо указать значения из примера выше |
http_address | Имя хоста, на котором будет работать oauth2-proxy. (см. пример в секцию "Конфигурация в Docker") |
upstreams | Хост, на котором работает Svacer (см. пример в секцию "Конфигурация в Docker") |
redirect_url | URL на который будет осуществлено перенаправление после успешной аутентификации на OAuth2 сервере (см. пример в секцию "Конфигурация в Docker") |
oidc_issuer_url | URL сервера OAuth2 для начала аутентификации (см. пример в секцию "Конфигурация в Docker"). С помощью данного URL будет получен список точек входа для поддержки разных потоков аутентификации (в терминах OAuth2) |
Детали работы oauth2-proxy с куками и токенами
Для корректной настройки oauth2-proxy полезно понимать детали работы ouath2-proxy. На шаге 2 прокси определяет необходимость выполнения аутентификации пользователя. Прокси проверяет наличие куки _oauth2_proxy и его срок действия. Срок действия создаваемых кук указывается в поле cookie_expire. Если срок действия куки закончен, то пользователь будут перенаправлен на точку входа прокси: /oauth2/sign_in.
Важным полем конфигурации является поле cookie_refresh. При каждом запросе, прокси обновляет свои куки с периодичностью, указанной в cookie_refresh. Вместе с обновлением своих куки прокси проверяет состояние сессии на OAuth2 сервере и получает новый токен доступа. То есть, если в поле cookie_refresh стоит значение 1 минута, 1 раз в минуту (если пользователь отправляет запросы 1 раз в 10 секунд, то каждый 6 запрос будет обновлять куки прокси) oauth2-proxy будет обновлять свои куки _oauth2_proxy и обновлять состоянии сессии аутентификации на сервере OAuth2 с получением токена доступа. Если сессия OAuth2 завершена, пользователь будет перенаправлен на точку входа аутентификации сервера OAuth2.
Выход пользователя из системы
Процедура выхода пользователя из Svacer состоит из двух этапов. На первом этапе производится инвалидация токена Svacer, далее осуществляется переход по ссылке указанной в параметре oauth_proxy.sign_out с тем, чтобы прокси сервер или/и OAuth сервер смогли выполнить инвалидацию сущностей аутентификации, выданных пользователю. Явным признаком корректной работы выхода пользователя из системы является отсутствие данных аутентификации в куках сервера OAuth (и/или прокси) после выхода пользователя из системы.
Работа со Svacer через cli
Работа в CLI происходит через специальную учетную запись в OAuth сервере, которая не требует аутентификации пользователя через web форму. Для этой цели используется Client Credentials Flow из спецификации OAuth2. На OAuth2 сервере требуется создание пары client@secret и настройки правил формирования JWT токена, соответствующего настройкам OIDC Svacer. Наиболее важным моментом является наличие такого поля в Claims токена, которое было указано в конфигурации Svacer, в поле token.claims.subject. После успешного входа на сервер Svacer, будет создан локальный пользователь Svacer, соответствующий субъекту токена OAuth2. Пользователю также будет назначена роль, указанная в token.claims.role, если она будет найдена. По этой причине рекомендуется перед первым использованием Svacer совместно с сервером OIDC настроить роли в Svacer.
Процесс аутентификации пользователя cli в Svacer с использованием OIDC сервера происходит следующим образом:
- Клиент запрашивает настройки сервера Svacer (по этой причине /api/settings должен проходить через прокси без аутентификации)
- В полученных настройках считывается значение, соответствующее полю oauth_proxy.sign_in
- Полученная на втором этапе ссылка, используется для получения токена доступа от сервера OAuth2
- Полученный на 3 шаге токен, обменивается на токен Svacer, с помощью запроса: POST; /api/public/oidc/login. Данный запрос должен быть включен в список запросов, не требующих аутентификации (см. пример конфигурации oauth2-proxy)
В виду всего вышесказанного, в конфигурационном файле oauth2-proxy надо делать исключения на
- запрос /api/public/oidc/login (иначе прокси будет пытаться аутентифицировать их, выдавая веб форму для аутентификации пользователя)
- запрос /api/settings
Возможные проблемы при настройке
Keycloak; Cookies not found
Проблема в DNS имена хоста Keycloak. Проблема встречается когда хост Keycloak имеет разные имена.
Keycloak; Invalid parametr: redirect_url
При перенаправлении пользователя на Keycloak сервер, OAuth2-proxy передал неверное значение в параметре redirect_uri. Необходимо проверить, что в настройках Keycloak и в параметре redirect_uri oauth2-proxy находятся одинаковые значения
OAuth2-proxy; CSRF cookie failed validation
<ref>
</ref>
Данная ошибка появляется по следующим причинам:
- csrf токен не найден в куках; Можно открыть консоль разработчика и убедится, что в куках для хоста oauth2-proxy есть кука: _oauth2_proxy_csrf_XXXXX
- csrf токен истек; Для решения проблемы, можно увеличить время действия csrf токена (см конфигурация OAuth2-proxy)
- был произведен перезапуск oauth2-proxy, а строка адреса в URL осталась от предыдущего запуска; Попробовать начать процедуру входа снова, перейдя на главную страницу oauth2-proxy
OAuth2-proxy; Строка переключения языка GUI Svacer и белый экран.
Сервер Svacer недоступен.
При работе с CLI возникает ошибка: Failed to query server. Details: server response error: 403
Не прописано исключение для запроса: /api/public/server/info
При работе с CLI возникает ошибка: cannot get authentication settings from server StatusCode:403
Не прописано исключение для запроса: /api/auth_settings
При работе с CLI возникает ошибка: ERROR: invalid character '<' looking for beginning of value
Не прописано исключение для запроса: /api/public/oidc/login
Дополнение
Ниже приводятся примеры конфигурации контейнеров для запуска Svacer в тестовом режиме с целью понимая логики работы oauth2-proxy совместно со Svacer и сервером Keycloak.
Образы
Svacer
Dockerfile:
FROM ubuntu:22.04 LABEL maintainer="akuzmin@ispras.ru" ARG DEBIAN_FRONTEND=noninteractive RUN apt update && apt install -y --no-install-recommends curl \ && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* \ && mkdir -p /svacer/bin && mkdir -p /svacer/store COPY svacer/bin/svacer-server /svacer/bin/ COPY svacer/bin/svacer /svacer/bin/ WORKDIR /svacer/bin ENV STORE=/svacer/store ENV SVACER_PG_URL=postgres://svace:svace@127.0.0.1:5432/svace ENV MEMSETTINGS=default ENV PATH=/svacer/bin:$PATH EXPOSE 8080 CMD /svacer/bin/svacer-server --memsettings=${MEMSETTINGS} run --store $STORE --pg $SVACER_PG_URL --config /config/config.yaml
Oauth2-proxy
Dockerfile:
FROM ubuntu:22.04 LABEL maintainer="chernykov_sv@ispras.ru" RUN mkdir /config RUN mkdir /logs #COPY oauth.cfg oauth.cfg WORKDIR /oauth COPY oauth2-proxy oauth2-proxy COPY start.sh start.sh RUN apt update && apt install curl -y ENTRYPOINT ["/bin/bash", "-c", "/oauth/start.sh"]
Скрипт start.sh (полезен для определения факта запуска Keycloak контейнера):
#!/bin/bash exit='no' start='no' tmt=60 echo "Using $KEYCLOAK_HOST" until [[ $exit == 'yes' ]]; do sleep 5 tmt=$(($tmt-5)) if [[ $tmt == 0 ]]; then exit='yes' fi resp=`curl --head -fsS http://$KEYCLOAK_HOST/health/ready` if [[ $? == 0 ]]; then exit='yes' start='yes' fi done if [[ $start == 'yes' ]]; then echo 'Starting oauth...' ./oauth2-proxy --config /config/oauth.cfg --logging-filename /logs/oauth.log else echo "Keycloak is not ready ...Can not start oauth2_proxy" fi
Docker-compose
version: "3.6" services: oauth_proxy: image: oauth_proxy container_name: oauth_proxy environment: - OAUTH2_PROXY_HTTP_ADDRESS=http://oauth_proxy:8080 - OAUTH2_PROXY_UPSTREAMS=http://svacer:8080 - OAUTH2_PROXY_REDIRECT_URL=http://oauth_proxy:4180/oauth2/callback - OAUTH2_PROXY_OIDC_ISSUER_URL=http://keycloak:8888/realms/svacer - KEYCLOAK_HOST=keycloak:8888 volumes: - ./config/proxy:/config - ./logs:/logs depends_on: svacer: condition: service_healthy ports: - "4180:8080" networks: svacer_oauth: ipv4_address: 10.10.10.2 postgresql: image: postgres:12.16 container_name: oauth_postgres restart: always shm_size: 1g environment: - POSTGRES_DB=svace - POSTGRES_USER=svace - POSTGRES_PASSWORD=svace - POSTGRES_ROOT_PASSWORD=svace expose: - "5432" volumes: - ./postgres_data:/var/lib/postgresql/data networks: svacer_oauth: ipv4_address: 10.10.10.3 healthcheck: test: pg_isready -U svace interval: 8s start_period: 16s timeout: 4s retries: 4 keycloak: image: keycloak:latest container_name: oauth_keycloak environment: - KEYCLOAK_ADMIN=admin - KEYCLOAK_ADMIN_PASSWORD=1234 - KEYCLOAK_LOGLEVEL=TRACE - KC_HEALTH_ENABLED=true command: - start-dev - --import-realm - --http-port=8888 volumes: - ./keycloak_realm:/opt/keycloak/data/import ports: - "8888:8888" expose: - "8888" networks: svacer_oauth: ipv4_address: 10.10.10.3 healthcheck: test: pg_isready -U svace interval: 8s start_period: 16s timeout: 4s retries: 4 svacer: image: oauth_svacer:latest container_name: oauth_svacer restart: always shm_size: 1g depends_on: postgresql: condition: service_healthy ports: - "3002:3002" - "18080:8080" volumes: - ./svacer_data:/data - ./config/svacer/:/config environment: - SVACER_PG_URL=postgres://svace:svace@postgresql:5432/svace - SVACER_DEBUG=true - SVACER_OPT='--config /config/oidc3.yaml' - STORE=/data/store networks: svacer_oauth: ipv4_address: 10.10.10.5 healthcheck: test: curl --fail http://localhost:8080/api/health || exit 1 interval: 8s start_period: 16s timeout: 4s retries: 4 networks: svacer_oauth: driver: bridge ipam: config: - subnet: 10.10.10.0/24 gateway: 10.10.10.1