Syntax highlighting of devops/keycloak

= Keycloak =

== Установка и запуск ==

После перебора различных вариантов установки и запуска Keycloak самым комфортным оказался 'docker-compose'.

Пример конфигурации Keycloak с letsencrypt, базой данных PostgreSQL и автоматическим бекапированием на S3:

{{{#!highlight yaml
---                                                                                                                    
services:                                                                                                              
  proxy:                                                   
    image: traefik:v3.3     
    command:
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=mail@example.com"                       # <---------------------------
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./letsencrypt:/letsencrypt

  keycloak:
    image: quay.io/keycloak/keycloak:26.0.1
    restart: unless-stopped
    command: start
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      KC_PROXY_ADDRESS_FORWARDING: "true"
      KC_HOSTNAME_STRICT: "false"
      KC_HOSTNAME_STRICT_HTTPS: "false"
      KC_HOSTNAME: keycloak.example.com                          # <---------------------------
      KC_HTTP_ENABLED: "true"
      KC_PROXY_HEADERS: xforwarded
      KEYCLOAK_ADMIN: tmpadmin
      KEYCLOAK_ADMIN_PASSWORD: secret                        # <---------------------------
      # ---
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/kc
      KC_DB_USERNAME: db_username                                 # <---------------------------
      KC_DB_PASSWORD: db_secret                                       # <---------------------------
      # ---
    labels:
      - "traefik.http.routers.kc.rule=Host(`keycloak.example.com`)"           # <---------------------------
      - "traefik.http.routers.kc.service=kc"
      - "traefik.http.routers.kc.entrypoints=websecure"
      - "traefik.http.routers.kc.tls.certresolver=myresolver"
      - "traefik.http.services.kc.loadbalancer.server.port=8080"

  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: db_username                                   # <---------------------------
      POSTGRES_PASSWORD: db_password                         # <---------------------------
      POSTGRES_DB: kc
      PGDATA: /var/lib/postgresql/data/pgdata
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - postgresql_data:/var/lib/postgresql/data/pgdata
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    command: >
      postgres -c max_connections=1000
               -c shared_buffers=256MB
               -c effective_cache_size=768MB
               -c maintenance_work_mem=64MB
               -c checkpoint_completion_target=0.7
               -c wal_buffers=16MB
               -c default_statistics_target=100
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U db_username -d kc" ]         # <---------------------------
      interval: 30s
      timeout: 10s
      retries: 5
    restart: unless-stopped
    tty: true
    stdin_open: true

  backup:
    image: eeshugerman/postgres-backup-s3:16
    environment:
      SCHEDULE: '@daily'
      BACKUP_KEEP_DAYS: 14
      S3_ENDPOINT: https://s3.endpoint.com                 # <---------------------------
      S3_REGION: my-region                                          # <---------------------------
      S3_ACCESS_KEY_ID: access_key                        # <---------------------------
      S3_SECRET_ACCESS_KEY: secret_key               # <---------------------------
      S3_BUCKET: bucket_name                                     # <---------------------------
      S3_PREFIX: backups                                               # <---------------------------
      POSTGRES_HOST: postgres
      POSTGRES_DATABASE: kc
      POSTGRES_USER: db_username                         # <---------------------------
      POSTGRES_PASSWORD: db_password               # <---------------------------

volumes:
  postgresql_data: {}
}}}

== Интеграция ==

=== Grafana ===

Источники:
 . [[ https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/generic-oauth/ ]]
 . [[ https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/keycloak/ ]]
 . [[ https://stackoverflow.com/questions/68741412/grafana-generic-oauth-role-assignment ]]

Интеграция с Grafana осуществляется в соответствии с документацией.
Однако, необходимо учитывать особенность: Grafana осуществляет поиск ролей в 'id_token', а не 'access_token'. По умолчанию, роли пользователя не попадают в id_token, поэтому надо подкрутить конфигурацию клиента -  [[ https://stackoverflow.com/questions/68741412/grafana-generic-oauth-role-assignment ]].

=== SSH ===

Пример конфигурации, которая позволяет предоставлять административный доступ к серверу через `ssh` путем назначения роли пользователю или группе.

План:
 1. настраиваем сервер
 2. устанавливаем расширение в Keycloak

На этапе настройки сервера устанавливаем [[https://github.com/kha7iq/kc-ssh-pam | kc-ssh-pam]]. В процессе установки можно внести ряд дополнений:

Запрещаем дальнейшие действия в случае неудачной проверки пользователя в Keycloak. Это может быть полезным, если локальный пользователь с таким же именем уже существует на сервере или в Keycloak такого пользователя. В таком случае, пользователь сможет зайти под локальной учетной записью, что не является предпочтительным.
{{{
# --- вместо
auth sufficient pam_exec.so expose_authtok      log=/var/log/kc-ssh-pam.log     /opt/kc-ssh-pam/kc-ssh-pam
auth optional pam_script.so
# --- использовать
auth [success=2 default=ignore] pam_exec.so expose_authtok      log=/var/log/kc-ssh-pam.log     /opt/kc-ssh-pam/kc-ssh-pam
auth optional pam_script.so
auth requisite pam_deny.so
}}}

В скрипте `/usr/share/libpam-script/pam_script_auth` добавляем строку 

{{{#!highlight bash
adduser $PAM_USER sudo
}}}
чтобы новый пользователь сразу добавлялся в группу `sudo`.

Также, здесь необходимо отключить пароль для `sudo`.

{{{#!highlight bash
sudo visudo

# --- вместо
%sudo  ALL=(ALL:ALL) ALL
# --- используем
%sudo ALL=NOPASSWD: ALL
}}}

На втором этапе устанавливаем расширение [[https://github.com/sventorben/keycloak-restrict-client-auth | keycloak-restrict-client-auth ]] для Keycloak. Это расширение нам требуется для ограничение доступа к Realm клиенту, созданному на предыдущем этапе. "Из коробки" (на сколько понимаю) Keycloak не предоставляет такую возможность.

Установку выполняем по [[https://github.com/sventorben/keycloak-restrict-client-auth/blob/main/README.md | README.md ]] для ''Client Role based'' режима. Здесь можно использовать более простой вариант аутентификации

{{devops/keycloak/ssh-flow|SSH authorization flow | width=50%}}

Кроме того, требуется указать новый способ аутентификации в настройках клиента ''ssh-login'': Clients > ssh-login > Advanced > Authentication flow overrides > Direct Grant Flow

По итогу получаем, что при назначении роли `restricted-access` пользователю или группе предоставляется `ssh` доступ с ''sudo'' к преднастроенным серверам.


== Рецепты ==

=== Восстанавливаем доступ администратора ===

Для восстановления пароля требуется доступ к серверу, на котором установлен Keycloak.

Создаем временного администратора
{{{#!highlight bash
bin/kc.sh bootstrap-admin user
}}}

Далее:

 1. заходим под временным администратором,
 2. сбрасываем пароль,
 3. заходим под администратором,
 4. удаляем временного администратора.