Начал писать свой пэт проект и дошел до момента, когда мне понадобилось авторизовывать пользователя в своем приложении. До этого был опыт использования спринга с кейклок, но поскольку сейчас развернул приложение в облаке, решил узнать, как принято использовать кейклок в облаке. Может можно использовать кейклок как авторизационную прокси?
Для начала, у меня были приложение развернутое в кубере и ingress сервис. Для начала решил использовать kubectl, а не helm, чтобы разобраться что-за что отвечает, а после по надобности перейти на helm. Собственно этой командой у меня был настроен ингрес.
kubectl --namespace default apply -f - <<< '
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: app
port:
number: 80
ingressClassName: nginx
'
Покопавшись немного в интернете нашел, что ингресс может делегировать авторизацию c помощью аннотаций, а сервис которому можно так делегировать называется oauth2-proxy.
oauth2-proxy
Сам по себе oauth2-proxy не умеет авторизовывать пользователей. Он по сути является адаптером между ingress и сервисами которые можно использовать для авторизации. В документации у них есть инструкции как использовать для авторизации гугл, гитхаб, гитлаб... Для начала решил не настраивать сервис кеклока, а поэкспериментировать с чем-то готовым, например авторизовывать через гитхаб.
По сути, действуя по инструкции настроил oauth2-proxy, добавил только две настройки:
1) --set-xauthrequest - добавляет в запрос хэдеры X-Auth-Request-User, X-Auth-Request-Groups, X-Auth-Request-Email и X-Auth-Request-Preferred-Username.
2) --set-authorization-header - добавляет хедер Authorization: Bearer...
kubectl --namespace default apply -f - <<< '
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
replicas: 1
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:latest
imagePullPolicy: Always
ports:
- containerPort: 4180
protocol: TCP
args:
- --provider=github
- --email-domain=*
- --upstream=file:///dev/null
- --http-address=0.0.0.0:4180
- --scope=user:email
- --set-xauthrequest=true
- --set-authorization-header=true
env:
- name: OAUTH2_PROXY_CLIENT_ID
value: acb0636a63a40efab6d
- name: OAUTH2_PROXY_CLIENT_SECRET
value: 4076962096c9b3d1774e9cb6850b67eef23ad88e
- name: OAUTH2_PROXY_COOKIE_SECRET
value: 6Pf1nzU4nV2syd8e0JAkmw==
---
apiVersion: v1
kind: Service
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
ports:
- name: http
port: 4180
protocol: TCP
targetPort: 4180
selector:
app: oauth2-proxy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-oauth2-proxy
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /oauth2
pathType: Prefix
backend:
service:
name: oauth2-proxy
port:
number: 4180
ingressClassName: nginx
'
Дальше надо проставить переменные окружения (в целом можно так же в аргументах передать):
OAUTH2_PROXY_CLIENT_ID - берется и приложения в git-hub
OAUTH2_PROXY_CLIENT_SECRET - так же берется из приложения, но показывается только один раз, после нужно создавать новый
OAUTH2_PROXY_COOKIE_SECRET - можно сгенерировать так python ‑c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
Остается только добавить аннотации в ingress сервис и авторизация заработает.
1) nginx.ingress.kubernetes.io/auth-url - судя по инструкция в интернетах обычно можно указать через https://$host... но у меня не получалось ни в какую. Появлялась проблема ssl сертификата, возможно он не совсем правильно настроен был. Поэтому я этот url прописываю внутрекуберовский.
2 nginx.ingress.kubernetes.io/auth-signin: - этот url вернется пользователю, на неудачную авторизацию.
3) nginx.ingress.kubernetes.io/auth-response-headers: - здесь ингрес добавляет указанные хэдеры из ответа oauth2-proxy в запрос
4) nginx.ingress.kubernetes.io/proxy-buffer-size: - размер кэша, нужен, чтобы на каждый запрос не ходить за авторизацией.
kubectl --namespace default apply -f - <<< '
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
annotations:
nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.default.svc.cluster.local:4180/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email, authorization"
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: app
port:
number: 80
ingressClassName: nginx
'
Заходим теперь в https://somehost.ru/app и нас перенаправляет на страничку авторизации в git-hub.
Keycloak
Так, теперь у меня есть какая-никакая авторизация, но хочется ее самому контролировать. Пришло время перевести oauth2-proxy на кейклок. И сначала я чуть-чуть ошибся. Очень много инструкций в интернете рассказывают, как настраивать кейклок до 16й версии. Я его настроил, посмотрел как подключить русских провайдеров типа вк яндекс итд и понял, что нужно настроить 19ю версию. В общем обратите на это внимание, 19 версия лучше, легче быстрее и всякое такое. По крайней мере в документации так написано.
Собственно настройка кейклок у меня выглядит так:
kubectl --namespace default apply -f - <<< '
apiVersion: v1
kind: Service
metadata:
name: keycloak
labels:
app: keycloak
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: keycloak
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: default
labels:
app: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: playaru/keycloak-russian
args: [ "start" ]
env:
- name: KC_HOSTNAME_URL
value: "https://somehost.ru"
- name: KC_HOSTNAME_ADMIN_URL
value: "https://somehost.ru"
- name: KC_HOSTNAME_STRICT
value: "false"
- name: KC_HTTP_ENABLED
value: "true"
- name: KEYCLOAK_ADMIN
value: "keycloak"
- name: KEYCLOAK_ADMIN_PASSWORD
value: "keycloak"
- name: KC_PROXY
value: "edge"
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_DB
value: "postgres"
- name: KC_DB_URL
value: ""
- name: KC_DB_URL_DATABASE
value: "keycloak"
- name: KC_DB_USERNAME
value: ""
- name: KC_DB_PASSWORD
value: ""
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /realms/master
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-test
namespace: default
spec:
tls:
- hosts:
- somehost.ru
secretName: k8s-secret
ingressClassName: nginx
rules:
- host: somehost.ru
http:
paths:
- path: /admin
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
- path: /realms/
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
- path: /resources/
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
- path: /robots.txt
pathType: ImplementationSpecific
backend:
service:
name: keycloak
port:
number: 80
- path: /js/
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
'
Если не указывать настройки базы данных, то кейклок создаст базу сам, а мне нужно было подключиться к своей. И image я взял playaru/keycloak-russian
, чтобы у меня сразу были русские провайдеры и не мучиться с их подключением.
Помимо настроек сервиса надо прокинуть через ингрес путь в keycloak, поскольку теперь страничку авторизации будет возвращать он. Путь /admin лучше не пробрасывать, можно например добавить vpn чтобы подключаться к сервисам внутри кубера, но я так не научился еще.
Теперь нужно поменять настройки oauth2-proxy.
kubectl --namespace default apply -f - <<< '
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
replicas: 1
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:latest
imagePullPolicy: Always
ports:
- containerPort: 4180
protocol: TCP
args:
- --provider=keycloak-oidc
- --upstream=file:///dev/null
- --http-address=0.0.0.0:4180
- --whitelist-domain=somehost.ru
- --cookie-domain=somehost.ru
- --set-xauthrequest=true
- --reverse-proxy=true
- --set-authorization-header=true
- --oidc-issuer-url=https://somehost.ru/realms/master
- --ssl-insecure-skip-verify=true
- --silence-ping-logging=true
- --email-domain=*
env:
- name: OAUTH2_PROXY_CLIENT_ID
value: ingress
- name: OAUTH2_PROXY_CLIENT_SECRET
value: <client-secret>
- name: OAUTH2_PROXY_COOKIE_SECRET
value: zTBum01F+fZmVntQutehXw==
---
apiVersion: v1
kind: Service
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
ports:
- name: http
port: 4180
protocol: TCP
targetPort: 4180
selector:
app: oauth2-proxy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-oauth2-proxy
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /oauth2
pathType: Prefix
backend:
service:
name: oauth2-proxy
port:
number: 4180
ingressClassName: nginx
'
Здесь настройки такие добавились:
1) --oidc-issuer-url - url по которому кейклок вернет настройки для авторизации, регистрации и подобное.
2) --ssl-insecure-skip-verify - как раз здесь я выключаю проверку сертификата, потому что не получалось настроить удачный поход внутри кубера. С моим сертификатом явно что-то не так.
3) --silence-ping-logging - буквально "не пиши в логи пинг кубера"
Как настроить кейклок и где взять client-secret хорошо описано тут.
Теперь при входе на страницу https://somehost.ru/app нам откроется авторизация в кейклоке.
Выводы
В целом, получилось сделать то что хотелось, авторизация работает. Но нужно еще разобраться с сертификатом да и наверняка много с чем еще.
Ну и как всегда телега, хотя в прошлые разы подписываться на нее не было никакого смысла, но я стараюсь)
Комментарии (5)
DonAlPAtino
12.12.2023 06:58А как вы в само приложение данные аутентифицированного пользователя пробрасываете? (Сорри если вопрос ламерский - я пока в кубах плаваю совсем)
evilrussian
12.12.2023 06:58oauth2-proxy прокитывает дальше в приложение jwt access token в заголовке Authorization, который прилетает из браузера. oauth2-proxy выступает как реверс-прокси перед приложением.
Andrey210 Автор
12.12.2023 06:58В ingress севисе прописываю в анноцютации
nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email, authorization"
А в oauth2-proxy проставляю параметр
--set-authorization-header=true
Тогда в ее ответе будут нужные хэдеры, которые подставятся в реквест. В случае с кейклоком в хэдере x-auth-request-user будет id пользователя, как будет с гитхабом-не проверял.
evilrussian
Если быть точным, то аутентификация а не авторизация (простите)
Кроме oauth-proxy есть еще gogatekeeper.
КК 19 версии уже староват, берите лучше для тестов самый свежий 23, плюс используйте Statefulset.
Если используете KC_HOSTNAME_STRICT=false, то переменная KC_HOSTNAME_URL не нужна.
Если вывешиваете КК через Ingress, то надо брать сервис ClusterIP а не LoadBalancer.
Andrey210 Автор
Спасибо! Попробую.