Одно из правил, которых мы должны придерживаться при создании ролей или плейбуков Ansible — идемпотентность: если роль выполняется много раз, целевой результат не должен изменяться.
Вот пример: если у вас есть публичные ключи ssh для пользователей, которые вы раскидываете в их каталоги .ssh, проблем нет — новый ключ будет добавлен только при изменении файла с публичным ключом. Но что же будет, если мы в роли или плейбуке пытаемся задать пароли пользователей через модуль user? Не забываем кстати, что пароли хотя бы должны быть зашифрованы с помощью vault либо, как вариант, их можно вводить интерактивно во время выполнения роли. В чем здесь подвох? Сам механизм добавления пароля в Linux работает таким образом, что в /etc/shadow, где хранятся хеши паролей, никогда не будет одинаковых значений хэша, даже если мы задаём разным пользователям один и тот же простой пароль. Это происходит за счет того, что кроме нашего пароля Linux добавляет так называемую «соль» в хэш-функцию, что позволяет избежать проблемы простых и одинаковых паролей (чтоб защититься от атак по словарю).
# useradd bob
# passwd bob
New password:
Retype new password:
passwd: password updated successfully
# grep bob /etc/shadow
bob:$6$R22DWjRz5wd1iCqM$zJ98FQY5ghKj2A2DuoUf/ZxkdKMyjKIF6oheDGt4XBUgx4d6nLHOWYzbC3NU2hhMWgo/rxDp0M5g6mheTUiTc1:19074:0:99999:7:::
$6$
— алгоритм, используемый для хэш-функции. Здесь SHA-512. Менять в /etc/login.defs в переменной ENCRYPT_METHOD.
$R22DWjRz5wd1iCqM$
— значение между двумя знаками доллара и есть «соль».
zJ98FQY5ghKj2A2DuoUf/ZxkdKMyjKIF6oheDGt4XBUgx4d6nLHOWYzbC3NU2hhMWgo/rxDp0M5g6mheTUiTc1
— часть до двоеточия — захэшированный пароль, в качестве входных аргументов функции хэша используется соль и заданный нами пароль.
19074
— дата последнего изменения пароля, считается от рождества, но не Христова, а Unix — c 1 января 1970 года.
0
— минимальное кол-во дней между сменами пароля. При нуле пароль менять не требуется (кроме собственного желания).
99999
— максимальное разрешенное число дней между сменами пароля. При всех девятках пароль никогда не просрочится.
7
— число дней для отображении предупреждения о смене пароля. По умолчанию предупреждают за неделю.
Остальные значения не заданы, их назначение есть в документации.
Функция для «соли» определена в файле и каждый раз возвращает рандомное (случайное) значение.
!!! note “Кому интересно копнуть глубже”
Команда создания пароля passwd
задана в исходниках src/passwd.c
(язык Си) в библиотеке shadow-*
. Исходники. Новый пароль создается с помощью функции pw_encrypt(plain,salt)
.
«Соль» (salt) создаётся функцией crypt_make_salt()
из исходных кодов shadow-*/libmisc/salt.c
. Эта функция использует переменную окружения ENCRYPT_METHOD
. Если её нет, проверяется другая переменная окружения MD5_CRYPT_ENAB
.
const char *method;
(…)
{
method = getdef_str ("ENCRYPT_METHOD");
if (NULL == method) {
method = getdef_bool ("MD5_CRYPT_ENAB") ? "MD5" : "DES";
}
При стандартном алгоритме шифрования SHA512 функция crypt_make_salt()
создаёт соль соединением символов ’ ’ и псевдорандомным числом:
if (0 == strcmp (method, "SHA512")) {
MAGNUM(result, '6');
strcat(result, SHA_salt_rounds((int *)arg));
salt_len = SHA_salt_size();
Функция pw_encrypt для пароля passwd вызывает функцию crypt из библиотеки libc. Функция crypt выбирает метод шифрования в зависимости от префикса соли.
====
Генерируемое случайное значение «соли» — проблема для идемпотентности Ansible. Получается, что даже если мы не изменяем пароль пользователя, каждый раз при выполнении роли хэш будет пересоздаваться, т.к. будет использоваться новое сгенерированное Linux-ом значение «соли». И каждый раз у нас после выполнения модуля будет changed — свидетельство того, что файл /etc/shadow
снова поменялся. Как сделать идемпотентным изменение пароля, описано в документации Ansible:
- name: Change password for user {{ linux_user }}
user:
name: "{{ linux_user }}"
password: "{{ user_password | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}"