Начал писать свой пэт проект и дошел до момента, когда мне понадобилось авторизовывать пользователя в своем приложении. До этого был опыт использования спринга с кейклок, но поскольку сейчас развернул приложение в облаке, решил узнать, как принято использовать кейклок в облаке. Может можно использовать кейклок как авторизационную прокси?

Для начала, у меня были приложение развернутое в кубере и 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)


  1. evilrussian
    12.12.2023 06:58

    Если быть точным, то аутентификация а не авторизация (простите)

    Кроме oauth-proxy есть еще gogatekeeper.

    КК 19 версии уже староват, берите лучше для тестов самый свежий 23, плюс используйте Statefulset.

    Если используете KC_HOSTNAME_STRICT=false, то переменная KC_HOSTNAME_URL не нужна.

    Если вывешиваете КК через Ingress, то надо брать сервис ClusterIP а не LoadBalancer.


    1. Andrey210 Автор
      12.12.2023 06:58

      Спасибо! Попробую.


  1. DonAlPAtino
    12.12.2023 06:58

    А как вы в само приложение данные аутентифицированного пользователя пробрасываете? (Сорри если вопрос ламерский - я пока в кубах плаваю совсем)


    1. evilrussian
      12.12.2023 06:58

      oauth2-proxy прокитывает дальше в приложение jwt access token в заголовке Authorization, который прилетает из браузера. oauth2-proxy выступает как реверс-прокси перед приложением.


    1. 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 пользователя, как будет с гитхабом-не проверял.