Мы в нашей организации, как и многие, переходим на отечественные продукты, коснулось это и среды контейнеризации. За годы эксплуатации мы нежно полюбили OKD (Openshift) и очень расстраивались в ванильном kubernetes, подмечая отсутствие ставших уже привычными вещей. Однако OKD состоит из свободно распространяемых компонентов, а значит что-то да можно переиспользовать, например, web-консоль. Ее то мы и решили перенести в насколько это возможной полноте функционала. Существующие гайды обычно покрывают лишь установку самой консоли, нам же хотелось использовать и SSO и дополнительные элементы консоли - каталог ссылок и объявления в заголовке.

Итак, нам потребуется:

  • Сертификаты для обслуживания web-console

  • OIDC провайдер. В нашем случае Keycloak

  • Кластер kubernetes

  • Образ openshift-console из quay.io

  • Репозитарий с CRD для web-консоли

  • cli от openshift (опционально, но команды cli придется адаптировать самостоятельно)

1. Настраиваем клиент в OIDC провайдере

Создаем клиент keycloak. Все изображения с примерами настройки спрячу под спойлер.

  1. client ID: kubernetes

  2. root url: <url консоли>

  3. valid redirect urls: /*

  4. Client authentication: on

  5. Сохраняем клиент

  6. Внутри клиента, во вкладке client scopes добавляем audience

    1. переходим в scope kubernetes-dedicated (либо <cliendID>-dedicated если ID другой)

    2. Жмем Configure a new mapper

    3. Выбираем тип: Audience

    4. прописываем имя и выбираем included client audience: kubernetes (cliendID)

    5. Add to ID token: on

    6. Остальные параметры оставляем по умолчанию, жмем save

  7. В глобальной вкладке client scopes (слева) добавляем Groups:

    1. name: groups

    2. Type: Default

    3. Переходим во вкладку Mappers, жмем Configure a new mapper

      1. выбираем Group Membership

      2. name: groups

      3. Token Claim Name: groups

      4. Full group path: off

    4. Сохраняем изменения

Скрытый текст

Важно: Это рабочий конфиг, однако после необходимо захарденить клиент согласно политикам безопасности в вашей организации.

Сохраняем конфиг, сохраняем себе Client ID и Client Secret (вкладка Credentials). Также нам потребуется Issuer, взять его можно из раздела realm settings -> OpenID Endpoint Configuration.

Пример настройки:
Настройка client
Настройка client
6. Настройка Audience
6. Настройка Audience
7. Настройка глобального Client scope
7. Настройка глобального Client scope

2. Настройка kubernetes

Для того, чтобы консол�� могла выполнять действия от имени пользователя, необходимо чтобы кластер kuebrentes мог аутентифицировать запросы с использование вашего jwt-токена. Наш кластер разворачивался через kubeadm, для других инсталляций локации конфигов могут отличаться

  1. На ВМ с kubernetes apiserver необходимо добавить сертификат, который используется в keycloak по пути: /etc/kubernetes/pki/oidc-ca.crt

  2. Добавляем конфиг oidc в kubernetes apiserver:

/etc/kubernetes/manifests/kube-apiserver.yaml
...
spec:
  containers:
  - command:
    - kube-apiserver
    ...
    - --oidc-issuer-url=https://auth.keycloak.myinfra.zone/auth/realms/myrealm
    - --oidc-client-id=kubernetes
    - --oidc-username-claim=preferred_username
    - --oidc-groups-claim=groups
    - --oidc-ca-file=/etc/kubernetes/pki/oidc-ca.crt
    - --oidc-username-prefix=-
...
  • oidc-issuer-url - урл из OpenID Endpoint Configuration

  • oidc-client-id - client id

  • oidc-username-claim - атрибут из jwt по которому определяется логин пользователя

  • oidc-groups-claim - claim, в котором содержится список групп

  • oidc-ca-file - путь до файла с сертификатом keycloak

  • oidc-username-prefix - префикс, который добавляется к логину пользователя внутри kubernetes (например, для rolebinding)

После перезапуска подов должны подтянуться новые настройки, проверить доступ можно сгенерировав access токен и обратившись с ним в kube-api

Скрытый текст
curl -k -L -X POST 'https://auth.keycloak.myinfra.zone/auth/realms/myrealm/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=kubernetes' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_secret=<KUBERNETES_CLIENT_ID_TOKEN>' \
--data-urlencode 'scope=openid' \
--data-urlencode 'username=<KEYCLOAK_USER>' \
--data-urlencode 'password=<KEYCLOAK_USER_PASSWORD>'
oc login --token=ACCESS_TOKEN_HERE --server=https://apiserver_url:6443

3. Настройка openshift-console в кластере

Нам остается только грамотно настроить манифесты:

Скрытый текст

Вспомогательные манифесты, namespaces, serviceaccouns, clusterrolebindings:

---
kind: Namespace
apiVersion: v1
metadata:
  name: openshift-console
---
kind: Namespace
apiVersion: v1
metadata:
  name: openshift-console-user-settings
---
kind: ServiceAccount
apiVersion: v1
metadata:
  name: console
  namespace: openshift-console
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: okd-console-role
subjects:
  - kind: ServiceAccount
    name: console
    namespace: openshift-console
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
---

Сертификаты для https консоли и ca для доверия к keycloak:

kind: Secret
apiVersion: v1
metadata:
  name: console-serving-cert
  namespace: openshift-console
data:
  ca.crt: >-
    ca_cert_base_64
  tls.crt: >-
    public_cert_base_64
  tls.key: >-
    private_cert_base_64
type: kubernetes.io/tls

Deployment:

Скрытый текст
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: console
  namespace: openshift-console
  labels:
    app: console
    component: ui
spec:
  replicas: 1
  selector:
    matchLabels:
      app: console
      component: ui
  template:
    metadata:
      name: console
      creationTimestamp: null
      labels:
        app: console
        component: ui
    spec:
      nodeSelector:
        node-role.kubernetes.io/control-plane: ''
      restartPolicy: Always
      serviceAccountName: console
      schedulerName: default-scheduler
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: component
                    operator: In
                    values:
                      - ui
              topologyKey: kubernetes.io/hostname
      terminationGracePeriodSeconds: 30
      securityContext: {}
      containers:
        - name: console
          image: quay.io/openshift/origin-console:4.12.0
          command:
            - /opt/bridge/bin/bridge
            - '--public-dir=/opt/bridge/static'
            - '--control-plane-topology-mode=HighlyAvailable'
            - '--k8s-public-endpoint=https://kubernetes_apserver:6443'
            - '--listen=http://[::]:8080'
            - '--k8s-auth=oidc'
            - '--k8s-mode=in-cluster'
            - '--tls-cert-file=/var/serving-cert/tls.crt'
            - '--tls-key-file=/var/serving-cert/tls.key'
            - '--base-address=https://console.apps.myinfra.zone'
            - '--user-auth=oidc'
            - '--user-auth-oidc-ca-file=/var/serving-cert/ca.crt'
            - '--user-auth-oidc-client-id=kubernetes' # the same as for kubernetes apiserver client
            - '--user-auth-oidc-client-secret=oidc_client_from_keycloak'
            - '--user-auth-logout-redirect=https://console.apps.myinfra.zone'
            - >-
              -user-auth-oidc-issuer-url=https://auth.keycloak.myinfra.zone/auth/realms/myrealm
          resources: {}
          volumeMounts:
            - name: console-serving-cert
              readOnly: true
              mountPath: /var/serving-cert
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
              scheme: HTTP
            timeoutSeconds: 1
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 150
            timeoutSeconds: 1
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 3
          ports:
            - name: https
              containerPort: 8443
              protocol: TCP
            - name: http
              containerPort: 8080
              protocol: TCP
          imagePullPolicy: IfNotPresent
      serviceAccount: console
      volumes:
        - name: console-serving-cert
          secret:
            secretName: console-serving-cert
            defaultMode: 420
      dnsPolicy: ClusterFirst
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 3
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

Сервис и ingress:

---
kind: Service
apiVersion: v1
metadata:
  name: console
  namespace: openshift-console
spec:
  ports:
    - name: https
      protocol: TCP
      port: 443
      targetPort: 8443
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
  internalTrafficPolicy: Cluster
  type: ClusterIP
  selector:
    app: console
    component: ui
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
  name: okd-console
  namespace: openshift-console
  annotations:
    nginx.ingress.kubernetes.io/affinity: cookie
    nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
    nginx.ingress.kubernetes.io/ssl-passthrough: 'false'
spec:
  ingressClassName: nginx
  tls:
    - secretName: console-serving-cert
  rules:
    - host: console.apps.myinfra.zone
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: console
                port:
                  number: 80

В зависимости от ваших требований можно включить ssl-passthrough, не забыв при этом поменять listen port в приложении и поправить раздел rules у ingress, однако я столкнулся с проблемой, что при такой конфигурации не работает > 1 пода - трафик round-robin отправляется на поды и возможны потери сессии.

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

4. Включаем console links, ConsoleNotification, ConsoleCLIDownload

Все что нам надо, это взять из репозитария манифесты CRD и применить их в кластере:

Скрытый текст
oc apply -f https://raw.githubusercontent.com/openshift/api/refs/heads/release-4.12/console/v1/0000_10_consolelink.crd.yaml
oc apply -f https://raw.githubusercontent.com/openshift/api/refs/heads/release-4.12/console/v1/0000_10_consoleclidownload.crd.yaml
oc apply -f https://raw.githubusercontent.com/openshift/api/refs/heads/release-4.12/console/v1/0000_10_consolenotification.crd.yaml

При желании можно применить и другие CRD console<type> из репозитория. Консоль подхватит и их.

После чего можно создавать соответствующие объекты (API Explorer -> group: console.openshift.io) и наблюдать эффект:

Вот и все, вы запустили web-консоль OKD с авторизацией, оповещениями и настраивае��ыми ссылками!