Как я KeyCloak побеждал

Как я себя чувствовал

Как я себя чувствовал

Привет, Хабр! Меня зову Амир и я хотел бы сегодня поделиться с Вами своим опытом поднятия сервиса SSO на базе решения KeyCloak.

Вводные:

Требования со стороны бизнеса:

  • Для внутренних сервисов компании требуется единая точка входа с подключением пользователей из существующей Active Directory.

  • Требуется что бы пользователь мог иметь доступ к одному или нескольким сервисам (в каждом из сервисов имел одну или несколько ролей). Если доступ к тому или иному сервису отсутствует, сообщать ему об этом.

Требования взаимодействия с КС

  • KC должен работать по https.

  • На стороне фронта будет использоваться пакет от KC https://www.npmjs.com/package/keycloak-js.

  • Возможность отправлять события в Kafka

Требования развертки

Версия KC: 25.0.2, так же проверял на версии 26.0.0 (тоже норм)

Ну вот с вводными разобрались, теперь приступим к реализации.

Build and deploy

Dockerfile

FROM keycloak/keycloak:25.0.2
COPY keycloak-kafka-1.1.5.jar /opt/keycloak/providers/

ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

Для подключения Kafka к CK используем keycloak-kafka-1.1.5

Dokcer-compose.yaml

version: "3.9"

services:
  keycloak:
    image: my_docker_hub/keycloak:latest
    volumes:
      - ./themes:/opt/keycloak/themes
      - ./cert/cert.jks:/etc/x509/https/truststore.jks
    container_name: keycloak
    ports:
      - "8443:8443"
    env_file: ./.env
    command: start
    depends_on:
      keycloak-postgres:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "exec 3<>/dev/tcp/127.0.0.1/9000;echo -e 'GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n' >&3;if [ $? -eq 0 ]; then echo 'Healthcheck Successful';exit 0;else echo 'Healthcheck Failed';exit 1;fi;",
        ]
      start_period: 10s
      interval: 30s
      retries: 3
      timeout: 5s

  keycloak-postgres:
    container_name: keycloak-postgres
    image: postgres
    volumes:
      - ./db/data:/var/lib/postgresql/data
    env_file: ./.env
    healthcheck:
      test: pg_isready -d postgres
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s

.env

KC_FEATURES: preview
KC_HEALTH_ENABLED: true
KC_METRICS_ENABLED: true

KC_HOSTNAME: host_keycloak
KC_HTTPS_PORT: 8443
KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD
KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks
KC_PROXY_HEADERS: xforwarded

KEYCLOAK_ADMIN: admin 
KEYCLOAK_ADMIN_PASSWORD: admin

KAFKA_TOPIC: user.event.user
KAFKA_ADMIN_TOPIC: user.event.admin
KAFKA_CLIENT_ID: keycloak
KAFKA_BOOTSTRAP_SERVERS: BOOTSTRAP_SERVERS
KAFKA_EVENTS: LOGIN,LOGOUT

KC_DB: postgres
KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak
KC_DB_SCHEMA: public

POSTGRES_DB: keycloak
PGUSER: keycloak
POSTGRES_USER : keycloak
POSTGRES_PASSWORD : keycloak
PGPASSWORD: password

Процесс сборки и развертывания опускаю, т.к. там ничего интересного нет.

Настраиваем HTTPS (В моем случае у меня есть root cert)

На этом моменте я прям залип знатно, т.к. в интернетах приводятся вагон вариаций исполнения, но к сожалению много устаревших либо не подходящих по типу сертификата

  1. Копируем root сертификат в /opt/keycloak/cert

  2. Выполняем команды

    keytool -importkeystore -srckeystore cert.pfx -srcstoretype pkcs12 -destkeystore ./cert.jks -deststoretype pkcs12
    cd /opt/keycloak/cert

    Указываем пароль от сертификата cert.pfx и назначаем пароль для keystore

  1. В docker-compose прокидываем cert.jks

volumes:
      - ./cert/cert.jks:/etc/x509/https/truststore.jks
  1. В файле .env

    Прописываем следующие переменные

KC_HOSTNAME: host_keycloak
KC_HTTPS_PORT: 8443
KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD
KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks
KC_PROXY_HEADERS: xforwarded

После развертывания через Docker compose, можем открывать KC.
https://host_name:8443/

Стартовая страница KC

Стартовая страница KC

Заходим под admin / admin

Подключаем AD

Создаем свой Realm
Идем в User federation и создаем Ldap providers

d36fb4e1274723e07ed5cabe74a39cac.png

Далее действовал по описанию в статье

https://habr.com/ru/companies/swordfish_security/articles/533264/

Ребятам и Swordfish Security огромное спасибо за статью

По обычаю, принимаясь за задачу по KC хотел уже потратить день другой на блогах и форумах в поисках решения/гайда, как тут уже все написано :-)

Подключаем Kafka

Заходим в Realm settings, переходим в Events, в селекте выбираем kafka

8e83d8f26786d515a0ce683f83bc6eef.png

Теперь все события произведенные в админе будут отправляться в топик указанный в .env admin, а пользовательские события login/logout в топик user

Настройка Client

Создаем Client

d244307526b942b1fd41889ac5bd1b43.pngОставляем все настройки как есть

Оставляем все настройки как есть

После создания в настройках обязательно указываем разрешенные URL откуда можем обращаться и т.д.

aa944b3198ccea192f363223d595f13f.png

Создаем client scope audience, что бы добавить аудиенцию клиента в токен.

add client scope

add client scope

create client scope

create client scope

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

Audience

Audience

add mapper

add mapper

Name — указываете как удобно
Included Client Audience — выбираем нашего клиента

Сохраняем.

Добавляем наш scope нашему клиенту

Переходим в меню Clients — вкладка Client scopes — Add client scope

Add client scope

Add client scope

Выбираем наш scope и добавляем с признаком Default

Создание роли доступа для нашего client

Realm roles

Realm roles

Меню — Realm roles — Create role

Можно заполнить подобным образом

Можно заполнить подобным образом

Создание группы пользователей для доступа в наш client

Groups

Groups

Меню — Groups — Create group

Название я предпочитаю задавать для групп доступа = client id

Role mapping

Role mapping

Связываем роль доступа с нашей группой на вкладке Role mapping

Assign roles to my-client account

Assign roles to my-client account

Создание Flow для аутентификации через браузер для нашего клиента

Меню — authentication — Flows — дублируем browser

22bc0038d180d1bdda4b2e9a3d2dc21f.pngcbcb3dcf6bd58668f0b88f7bd2eb016c.png

Далее формируем следующую структуру

Странно, но не думал что ситуация которую мне нужно было разрешить, настолько редкая и по этой тепе в интернете было совсем мало инфы (нуууу очень мало). Решение нашел на stackoverflow, которое было представлено в виде скрина, которое в итоге было модифицировано :-)

Flow - Required: {  
    name: Login: <Название клиента> 
}
Step - Alternative: Cookie
Step - Alternative: Identity Provider Redirector config 
Flow - Alternative: {  
    name: gated browser form: <Название клиента> 
}

Step - Required: Username Form
Flow - Conditional: {  
    name: gated browser form - Conditional OTP Form config: <Название клиента> 
}
Condition - Required: Condition - user configured
Step - Required: OTP Form
Flow: {  
    name: RBA - Conditiona: <Название клиента> 
}
Condition - Required: Condition - user role
{
    Alias: user role <Название клиента>,
    User role: Выбираем роль созданную для нашего Client,
    Negate output: On
}
Step - Required: Deny access  
{
    Alias: Deny access config <Название клиента>,
    Error message: Доступ в приложение <Название приложения> запрещен
}

Condition - user role config

Condition — user role config

Deny access config

Deny access config

Должно получиться так

Должно получиться так

Далее, указываем наш созданный Flow как основной для Client

advanced

advanced

2b4e8d27136bffe5b677caf641b151d1.png

Если сейчас мы попробуем пройти авторизацию в KC через адаптер keycloak-js получим следующий результат

У Вас нет доступа в my-client

У Вас нет доступа в my-client

Добавление пользователя в группу доступа к my-client

Меню — members — Add member — выбираем пользователя из локальной базы или импортированного из AD

f2b1a6497a71dba5677f55540a4ab0ca.png

Далее пробуем пройти авторизацию через адаптер keycloak-js, получим результат «Успешная авторизация» с получением токена и редиректа на страницу указанную в настройках Client.

Итог

В этой мы рассмотрели реализацию авторизации в нашем приложении через популярное SSO ПО KeyCloak. Надеюсь было написано исчерпывающе, т.к. во время настройки всего я испытал много боли и может это кому то поможет.

Спасибо за прочтение.

© Habrahabr.ru