Мы в нашей организации, как и многие, переходим на отечественные продукты, коснулось это и среды контейнеризации. За годы эксплуатации мы нежно полюбили 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 с авторизацией, оповещениями и настраиваемыми ссылками!

Комментарии (0)