Перейти к основному содержимому

Аутентификация через JWT-прокси

Когда Semaphore работает за аутентифицирующим обратным прокси (например, Pomerium, Cloudflare Access или OAuth2 Proxy), прокси может передавать личность пользователя в заголовке с подписанным JWT. Semaphore проверяет JWT по конечной точке JWKS и автоматически создаёт или находит пользователя.

Так не приходится дублировать настройки OIDC в прокси и в Semaphore.

Как это работает​

  1. Обратный прокси аутентифицирует пользователя и добавляет подписанный JWT в настроенный HTTP-заголовок.
  2. Semaphore проверяет наличие этого заголовка в каждом запросе. Если он есть, токен проверяется по конечной точке JWKS.
  3. Если токен действителен, пользователь находится так же, как для LDAP и OIDC: сначала по внешней личности, сохранённой для claim sub токена, затем по адресу электронной почты, насколько это разрешает external_auth_email_matching, а иначе создаётся новый внешний пользователь. См. Как появляется запись пользователя.
  4. Если заголовок есть, но токен недействителен, Semaphore сразу возвращает 401 Unauthorized -- аутентификация по bearer-токену и по сессии не выполняется.
  5. Если заголовка нет, обычная аутентификация (bearer-токен, cookie сессии) выполняется как обычно.

Пользователи, созданные через JWT-аутентификацию, помечаются как внешние, а их личность сохраняется с идентификатором провайдера jwt и claim sub токена. Токен без claim sub отклоняется. Локальные (не внешние) пользователи никогда не сопоставляются по адресу электронной почты.

Прокси доверяют в отношении адреса электронной почты, который он помещает в токен. При значении external_auth_email_matching по умолчанию, auto, пользователь, созданный более ранней версией — которая сопоставляла пользователей JWT только по адресу электронной почты, — привязывается к своему sub при следующем запросе. При never такие пользователи не сопоставляются, и их запросы завершаются ошибкой, потому что новая учётная запись повторно использовала бы их адрес электронной почты. Дайте им один раз войти в режиме auto, прежде чем переключаться на never.

Вопросы безопасности​

Semaphore впускает любого, чей запрос содержит действительный токен в настроенном заголовке. Подпись не позволяет клиенту придумать токен, но не мешает повторно использовать уже имеющийся, поэтому прокси должен быть единственным путём внутрь:

  • Убедитесь, что к Semaphore нельзя обратиться в обход прокси. Привяжите его к частному адресу или закройте его порт межсетевым экраном; см. Сетевая безопасность.
  • Настройте прокси так, чтобы он удалял заголовок JWT из входящих запросов клиентов и устанавливал его только после аутентификации пользователя.
  • Задайте в audience значение, которое прокси использует для Semaphore. Без этого будет принят и токен, выпущенный прокси для другого приложения и подписанный теми же ключами.

Настройка​

Когда JWT-аутентификация включена, обязательны и header, и jwks_url. Semaphore проверяет это при запуске.

Файл конфигурации​

{
"auth": {
"jwt": {
"enabled": true,
"header": "X-Pomerium-Jwt-Assertion",
"jwks_url": "https://auth.example.com/.well-known/pomerium/jwks.json",
"audience": "https://semaphore.example.com",
"issuer": "https://auth.example.com"
}
}
}

Переменные окружения​

SEMAPHORE_JWT_AUTH_ENABLED=true
SEMAPHORE_JWT_AUTH_HEADER=X-Pomerium-Jwt-Assertion
SEMAPHORE_JWT_AUTH_JWKS_URL=https://auth.example.com/.well-known/pomerium/jwks.json
SEMAPHORE_JWT_AUTH_AUDIENCE=https://semaphore.example.com
SEMAPHORE_JWT_AUTH_ISSUER=https://auth.example.com

Пример для Docker​

docker run -d -p 3000:3000 --name semaphore \
-e SEMAPHORE_DB_DIALECT=sqlite \
-e SEMAPHORE_ADMIN=admin \
-e SEMAPHORE_ADMIN_PASSWORD=changeme \
-e SEMAPHORE_ADMIN_NAME=Admin \
-e SEMAPHORE_ADMIN_EMAIL=admin@localhost \
-e SEMAPHORE_JWT_AUTH_ENABLED=true \
-e SEMAPHORE_JWT_AUTH_HEADER=X-Pomerium-Jwt-Assertion \
-e SEMAPHORE_JWT_AUTH_JWKS_URL=https://auth.example.com/.well-known/pomerium/jwks.json \
-e SEMAPHORE_JWT_AUTH_AUDIENCE=https://semaphore.example.com \
-e SEMAPHORE_JWT_AUTH_ISSUER=https://auth.example.com \
semaphoreui/semaphore:latest

Параметры​

ПараметрПеременная окруженияОписание
auth.jwt.enabledSEMAPHORE_JWT_AUTH_ENABLEDВключить аутентификацию через JWT-прокси.
auth.jwt.headerSEMAPHORE_JWT_AUTH_HEADERHTTP-заголовок, содержащий JWT. Обязательный.
auth.jwt.jwks_urlSEMAPHORE_JWT_AUTH_JWKS_URLURL конечной точки JWKS для проверки подписи. Обязательный.
auth.jwt.audienceSEMAPHORE_JWT_AUTH_AUDIENCEОжидаемый claim aud. Если пусто, audience не проверяется.
auth.jwt.issuerSEMAPHORE_JWT_AUTH_ISSUERОжидаемый claim iss. Если пусто, издатель не проверяется.
auth.jwt.email_claimSEMAPHORE_JWT_AUTH_EMAIL_CLAIMClaim JWT с адресом электронной почты пользователя. По умолчанию: email.
auth.jwt.name_claimSEMAPHORE_JWT_AUTH_NAME_CLAIMClaim JWT с отображаемым именем пользователя. По умолчанию: name.
auth.jwt.username_claimSEMAPHORE_JWT_AUTH_USERNAME_CLAIMClaim JWT с именем пользователя. По умолчанию: email.
auth.jwt.signout_urlSEMAPHORE_JWT_AUTH_SIGNOUT_URLURL, на который ссылка выхода перенаправляет после очистки сессии Semaphore. См. Выход.

Поддерживаемые алгоритмы​

Semaphore принимает токены, подписанные ES256, ES384, ES512, RS256, RS384 или RS512. У каждого токена должен быть claim exp; токены без него отклоняются.

Выход​

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

Чтобы полностью завершить сеанс пользователя, задайте в signout_url конечную точку выхода прокси. После очистки сессии Semaphore браузер перенаправляется туда.

{
"auth": {
"jwt": {
"enabled": true,
"signout_url": "https://auth.example.com/.pomerium/sign_out"
}
}
}

Примеры для распространённых прокси:

  • Pomerium: https://<your-domain>/.pomerium/sign_out
  • OAuth2 Proxy: https://<your-domain>/oauth2/sign_out
  • Cloudflare Access: https://<your-team>.cloudflareaccess.com/cdn-cgi/access/logout

Доступность JWKS​

Semaphore загружает JWKS при запуске. Если конечная точка JWKS недоступна, сервер всё равно запускается. Набор ключей обновляется в фоне каждый час, а также когда токен подписан ключом с идентификатором, которого Semaphore ещё не видел, — не чаще одного раза в пять минут. Пока ключи недоступны, JWT-аутентификация возвращает ошибку.