Перенос пользователей из Keycloak в LDAP с сохранением паролей - PullRequest
0 голосов
/ 21 июня 2019

В настоящее время у нас есть сфера Keycloak, в которую мы хотим экспортировать всех существующих пользователей в LDAP (с сохранением их паролей), поэтому мы можем добавить поддержку входа в систему для других мест, которые не поддерживают OpenID Connect или SAML, но поддерживают LDAP.

Учитывая, что учетные данные пароля хранятся в пользовательской базе данных Keycloak MySQL с использованием алгоритма pbkdf2-sha256, мне пришлось использовать OpenLDAP 2.4.47 с модулями contrib для поддержки этого и на стороне LDAP (я также считаю, что включил этот модуль в настройках).

У меня проблемы с переносом существующего has (из Keycloak) в LDAP с использованием формата, описанного здесь: https://github.com/hamano/openldap-pbkdf2

Простите за использование PHP, но это язык программирования, с которым мне сейчас комфортнее всего.

Я пытался использовать поля HASH_ITERATIONS, SALT и VALUE вместе с пользовательской функцией base64url_encode, приведенной в комментариях под base64_encode в руководстве по PHP, для создания строки "Adapted Base64" и предоставления встроенной строки в качестве поле userPassword при создании пользователя LDAP. Пользователь успешно создан в LDAP, но я не могу пройти аутентификацию по LDAP, используя учетные данные для только что созданного пользователя.

/*
This example migrates a single user, to be adapted to a loop later, if it works.

NOTE: KC_* constants are defined in an external config.php file, such as:
KC_LDAP_BASEDN = "ou=users,dc=sso,dc=example,dc=com"
KC_REALM_ID = "KeycloakUserRealm"
(not actual code from config.php, but you get the point)
*/

$ldc = ldap_connect(KC_LDAP_SERVER);
ldap_set_option($ldc, LDAP_OPT_PROTOCOL_VERSION, KC_LDAP_PROTOCOL); // should be 3 in config.php
$ldb = ldap_bind($ldc, KC_LDAP_BINDDN, KC_LDAP_BINDPW);

$usertest = 'username_of_user_to_be_migrated';

// fetch user info
$u_sel = $db->query("SELECT * FROM `USER_ENTITY` WHERE `USERNAME`='".$db->real_escape_string($usertest)."' AND `REALM_ID`='".$db->real_escape_string(KC
_REALM_ID)."'");

$uinfo = $u_sel->fetch_assoc();

// fetch credential of user, type="password"
$c_sel = $db->query("SELECT * FROM `CREDENTIAL` WHERE `USER_ID`='".$db->real_escape_string($uinfo['ID'])."' AND `TYPE`='password'");

$cred = $c_sel->fetch_assoc();

$uprop = array();
$uprop['objectClass'] = array('top', 'person', 'organizationalPerson', 'inetOrgPerson');
$uprop['uid'] = $uinfo['USERNAME'];
$uprop['mail'] = $uinfo['EMAIL'];
$uprop['cn'] = $uinfo['FIRST_NAME'];
$uprop['sn'] = $uinfo['LAST_NAME'];

// also base64_decode() VALUE, since this seems to be already enocoded in base64, before re-encoding it with base64url_encode()
$uprop['userPassword'] = '{'.strtoupper($cred['ALGORITHM']).'}'.$cred['HASH_ITERATIONS'].'$'.base64url_encode($cred['SALT']).'$'.base64url_encode(base6
4_decode($cred['VALUE']));

ldap_add($ldc, 'uid='.$uprop['uid'].','.KC_LDAP_BASEDN, $uprop);

Как только это будет сделано, я запускаю второй скрипт, чтобы попытаться соединиться с этим пользователем:

$ldc = ldap_connect(KC_LDAP_SERVER);
ldap_set_option($ldc, LDAP_OPT_PROTOCOL_VERSION, KC_LDAP_PROTOCOL); // should be 3 in config.php

$userdn = 'uid=username_of_user_to_be_migrated,'.KC_LDAP_BASEDN;
$userpw = 'asdASD123';

$ldb = ldap_bind($ldc, $userdn, $userpw);

При выполнении второго сценария, приведенного выше, я просто получаю:

PHP Warning:  ldap_bind(): Unable to bind to server: Invalid credentials in /home/user/projectdir/test_ldap_user.php on line 22

Пример записи в базе данных CREDENTIAL выглядит примерно так:

             ID: 5718a65c-1927-4ac7-87ce-aec0c7dda296
         DEVICE: NULL
HASH_ITERATIONS: 27500
           SALT: � �??�Pz�e��X,
           TYPE: password
          VALUE: DdCJAJvuhidAC2by7TZY8I0E8HF4V6FXrPa4nSXduvSzbb+xHW3D4QiiiPpvuzL2bdk6k0kNQKS/477k5kiLzA==
        USER_ID: b06ce13f-4e8e-474e-b5ee-5d664d6f9575
   CREATED_DATE: 1561051801144
        COUNTER: 0
         DIGITS: 0
         PERIOD: 0
      ALGORITHM: pbkdf2-sha256

Вывод userPassword обычно выглядит примерно так:

{PBKDF2-SHA256}27500$FrcRlj8SGJJQet1l9LNYLA$DdCJAJvuhidAC2by7TZY8I0E8HF4V6FXrPa4nSXduvSzbb-xHW3D4QiiiPpvuzL2bdk6k0kNQKS_477k5kiLzA

Есть ли что-то, чего мне не хватает в этом коде, чтобы убедиться, что хэш пароля правильно перенесен?

Или уже есть сценарий / решение для миграции, которое использует Keycloak-> LDAP, которое я еще не нашел?

Заранее спасибо за любую помощь или подтолкнуть в правильном направлении.

1 Ответ

0 голосов
/ 25 июня 2019

Быстрое обновление: мне удалось найти решение вместе с одним из моих замечательных коллег, и я хотел опубликовать решение здесь, для дальнейшего использования, если кто-то еще застрянет на этом.

Мы обнаружили, что длина ключа, используемого для хеширования в Keycloak, составляла 64 байта, независимо от того, какой алгоритм использовался. Это также означало, что последовательность байтов хэша повторяется для тех алгоритмов, у которых длина ключа по умолчанию короче 64 байтов (чтобы сделать его ровно 64 байта). В PBKDF2 длина ключа по умолчанию для SHA-1 составляет 20 байтов, это 32 байта для SHA-256 и 64 байта для SHA-512. Это позволило мне создать следующую функцию PHP, которая возвращает строку того же типа, которая была возвращена slappasswd:

// Convert password credentials from Keycloak database to LDAP format (specific to PBKDF2 module)
// Parameter $cred is array from Keycloak database fields for 'CREDENTIAL' table
function password_keycloak_to_ldap($cred) {
        switch (strtolower($cred['ALGORITHM'])) {
                case 'pbkdf2':
                case 'pbkdf2-sha1':
                        $keybytes = 20;
                        break;
                case 'pbkdf2-sha256':
                        $keybytes = 32;
                        break;
                default:
                        $keybytes = 64;
                        break;
        }
        $out = '{'.strtoupper($cred['ALGORITHM']).'}'.$cred['HASH_ITERATIONS'].'$'.ab64_encode($cred['SALT']).'$';
        $oldhash = base64_decode($cred['VALUE']);
        $newhash = substr($oldhash, 0, $keybytes);
        $out .= ab64_encode($newhash);
        return $out;
}

Также я установил дополнительные вспомогательные функции (скопированные из других источников):

// ab64_* functions adapted from Python's Passlib v1.7.1
function ab64_encode($data){
        return rtrim(strtr(base64_encode($data), '+', '.'), '=');
}

function ab64_decode($data){
        return base64_decode(strtr($data,'.', '+').str_repeat('=', 3-(3+strlen($data))%4));
}

Наконец, использование в скрипте, который я разместил в вопросе, заменяет строку userPassword следующим:

$uprop['userPassword'] = password_keycloak_to_ldap($cred);

Я надеюсь, что это поможет кому-либо еще с миграцией пользователей с Keycloak на LDAP, а также с сохранением пользовательских паролей.

...