#keycloak #openresty
Вопрос:
У меня есть клиент nginx/openresty для сервера блокировки ключей для авторизации с использованием openid. Я использую lua-resty-openidc, чтобы разрешить доступ к службам, расположенным за прокси-сервером.
Я создал двух клиентов в двух разных сферах для разных услуг.
Проблема в том , что после того, как пользователь проходит аутентификацию в первой области, например https://<my-server>/auth/realms/<realm1>/protocol/openid-connect/auth?response_type=codeamp;client_id=openrestyamp;state=...........
, он также может напрямую получить доступ к другой службе realm2
.
Что здесь происходит? Как я могу гарантировать, что пользователь сможет получить доступ к клиенту только в той области, в которой он прошел аутентификацию?
Как я могу гарантировать, что после выхода из системы пользователь больше не сможет получить доступ, пока он не войдет в систему заново?
[Правка-подробности] Мой nginx.conf для двух сервисов приведен ниже. Пользователь сначала получает доступ https://<my-server>/service_1/
и перенаправляется на keycloack, чтобы ввести свой пароль для realm1. Он предоставляет его и может получить доступ к service_1.
Однако, если после этого он попытается получить доступ https://<my-server>/service_2/
, ему больше не нужно проходить аутентификацию, но он может войти в систему, хотя service_2 относится к клиенту в другой области, с другим секретом client_secret!
….. местоположение /service_1/ {
access_by_lua_block {
local opts = {
redirect_uri_path = "/service_1/auth", -- we are send here after auth
discovery = "https://<my-server>/keycloak/auth/realms/realm1/.well-known/openid-configuration",
client_id = "openresty",
client_secret = "<client1-secret>",
session_contents = {id_token=true} -- this is essential for safari!
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
# I disabled caching so the browser won't cache the site.
expires 0;
add_header Cache-Control private;
proxy_pass http://<server-for-service1>:port1/foo/;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
……………………..
location /service_2/ {
access_by_lua_block {
local opts = {
redirect_uri_path = "/service_2/auth", -- we are send here after auth
discovery = "https://<my-server>/keycloak/auth/realms/realm2/.well-known/openid-configuration",
client_id = "openresty",
client_secret = "client2-secret",
session_contents = {id_token=true} -- this is essential for safari!
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
# I disabled caching so the browser won't cache the site.
expires 0;
add_header Cache-Control private;
proxy_pass http://<server-for-service2>:port2/bar/;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
[Правка-подробности 2]
Я использую lua resty openidc версии 1.7.2, но все, что я пишу, должно соответствовать версии 1.7.4, основанной на различии кода двух версий.
Из журналов уровня отладки я ясно вижу, что сеанс создается во время первого доступа, а затем повторно используется во второй области, что неверно, так как у второго доступа все еще есть токен для первой области… Вот как выглядит авторизация для realm2…:
2021/04/28 12:56:41 [debug] 2615#2615: *4617979 [lua] openidc.lua:1414: authenticate(): session.present=true, session.data.id_token=true, session.data.authenticated=true, opts.force_reauthorize=nil, opts.renew_access_token_on_expiry=nil, try_to_renew=true, token_expired=false
2021/04/28 12:56:41 [debug] 2615#2615: *4617979 [lua] openidc.lua:1470: authenticate(): id_token={"azp":"realm1","typ":"ID","iat":1619614598,"iss":"https://<myserver>/keycloak/auth/realms/realm1","aud":"realm1","nonce":"8c8ca2c4df2...b26"
,"jti":"1c028c65-...0994f","session_state":"0e1241e3-66fd-4ca1-a0dd-c0d1a6a5c708","email_verified":false,"sub":"25303e44-...e2c1757ae857","acr":"1","preferred_username":"logoutuser","auth_time":1619614598,"exp":1619614898,"at_hash":"5BNT...j414r72LU6g"}
Комментарии:
1. «он также может напрямую получить доступ к другой услуге в режиме реального времени». что вы конкретно имеете в виду под этим?
2. добавлено объяснение и соответствующие детали моего nginx.conf….
3. Вы используете пользователя главной области для тестирования ? если это так, то это может произойти.
4. Нет, оба царства не являются царством хозяев… Кроме того, попытался изменить имена клиентов, чтобы они были разными в каждой области… Не повезло….
Ответ №1:
Ладно, это заняло у меня некоторое время. Также может быть так, что большинство учебных пособий оставляют эту уязвимость (только в настройках, где один nginx использует несколько областей), когда доступ к аутентификации в одной области позволит получить доступ к аутентификации в любой другой.
типичный вызов аутентификации из учебников там:
location /service1/ {
access_by_lua_block {
local opts = {
redirect_uri_path = "/realm1/authenticated",
discovery = "https://<myserver>/keycloak/auth/realms/realm1/.well-known/openid-configuration",
client_id = "client1",
client_secret = <........>,
session_contents = {id_token=true} -- this is essential for safari!
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
# I disbled caching so the browser won't cache the site.
expires 0;
add_header Cache-Control private;
proxy_pass http://realm1-server:port/service1/;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /service2/ {
<same for ream2>
}
Похоже, на самом деле есть две проблемы
- Мы не проверяем идентификатор области (это уязвимость)
- Сеансы для двух областей кэшируются взаимозаменяемо (это приведет к ситуации, когда, если мы исправим (1), нам теперь будет разрешен доступ только к одной области и нам придется выйти из realm1 для доступа к realm2)
Решения: 1)Нам нужно явно проверить правильность области 2)Мы должны использовать одну таблицу сеансов для каждой области (обратите внимание, что, хотя это, по-видимому, также решает (1), это не так, если злоумышленник смешивает и сопоставляет идентификаторы сеансов со своим «специальным» браузером-по крайней мере, я думаю)
Для no2 не было документации, мне пришлось прочитать код openidc.lua, а оттуда код библиотеки, используемой этой библиотекой (session.lua)
Изменения заключаются в следующем: местоположение /сервис1/ {
access_by_lua_block {
local opts = {
redirect_uri_path = "/realm1/authenticated",
discovery = "https://<myserver>/keycloak/auth/realms/realm1/.well-known/openid-configuration",
client_id = "client1",
client_secret = <........>,
session_contents = {id_token=true} -- this is essential for safari!
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").authenticate(opts,nil,nil,{name=opts.client_id})
if (err or ( res.id_token.azp ~= opts.client_id ) ) then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
<..................no changes here................>
}
location /service2/ {
<same for ream2>
}