Ansible для детского сада. Часть 4. Первичная настройка конечного клиента
4.1 Теоретически все просто
4.2 Сначала готовим открытую часть сертификата без пароля через Putty в Windows и еще раз вспоминаем что генерирует Putty в Windows, а что нужно в ssh
4.3 Детский скрипт, и как не надо делать
4.4 Отлаживаем SSH, снова и опять
4.5 Проблемы с переносом строки и не только
4.6 Проверяем, что получилось
4.7 Почти переходим к второму Ansible плейбуку
4.8 Воюем с пробелами в Anisble и восстанавливаем пример из первой статьи
4.9 Возвращаемся к основной задаче
Ansible для детского сада в скольки то частях. Часть 5. Приделываем костыли
Ansible для детского сада. Часть 4. Первичная настройка конечного клиента
В предыдущей части оказалось, что в системе есть узкое место – первичная настройка.
4.1 Теоретически все просто:
пишем отдельный плейбук типа такого: Reddit Creating System Users for Ansible Execution
# From a clean system with root ssh access:
# - Install sudo
# - Add the ansible user
# - Enable wheel group for sudo
- name: add wheel group
group:
name: wheel
state: present
- name: add ansible user
user:
name: ansible
groups: wheel
password: "{{ ansible_become_password }}"
createhome: yes
home: /var/local/ansible
state: present
shell: /bin/bash
uid: 20000
- name: install sudo
apt:
name: sudo
state: installed
when: ansible_os_family == 'Debian'
- name: configure sudoers
lineinfile:
dest: /etc/sudoers.d/ansible
line: "%wheel ALL=(ALL) ALL"
state: present
create: yes
validate: 'visudo -cf %s'
owner: root
group: root
mode: 0440
- name: create ansible user authorized_keys file from id_ed25519
authorized_key:
user: ansible
manage_dir: no
path: /etc/ssh/ansible
key: "{{ lookup('file', ansible_ssh_key) }}"
Практически это означает ручную настройку для рута «еще раз». Как раз этого хочется избежать, потому что в том же Debian по умолчанию ssh для root закрыт. Изменить настройку можно. Но не нужно.
4.2 Сначала готовим открытую часть сертификата без пароля через Putty (PUTTYGEN) в Windows и еще раз вспоминаем что генерирует Putty в Windows, а что нужно в ssh
Это все помнят, но необходимо повторить еще раз.
По результатам опытов и вопросов «что ж не работает то» ключ я сделал на целых 4096 бит, а вовсе не по умолчанию в 2048. Вроде бы, должно и так работать, но ведь нет!
Когда PUTTYGEN генерирует пару ключей, открытый и закрытый ключ, то мы получаем два файла.
ex1_public.pkb – файл с открытым ключем. Можно было сделать без расширения, значения это не имеет
ex1_private.ppk – файл с закрытой частью ключа.
ex1_public выглядит как:
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "key-4-pikabu"
AAAABС(длинный ключ)
---- END SSH2 PUBLIC KEY ----
ex1_private.ppk выглядит как:
PuTTY-User-Key-File-3: ssh-rsa
Encryption: none
Comment: key-4-pikabu
Public-Lines: 6
AAAABС (длинный ключ на 6 строк, он же лежит в ex1_public.pkb)
Private-Lines: 14
AAABAF(длинный ключ)A==
Private-MAC: f(длинная последовательность)
если вы нажимали еще какие-то кнопки и настройки, то могут встречаться строки типа:
Key-Derivation: Argon2id
Argon2-Memory: 8192
Argon2-Passes: 34
В /home/user/.ssh/ authorized_keys лежит только строка из открытого ключа в формате:
ssh-rsa AAAABС.. (и так далее) вплоть до конца строки.
PUTTYGEN так и показывает , КАК НАДО.
4.3 Детский скрипт, и как не надо делать
Перепишу все, что выше, обратно в ssh \ bash:
В процессе отладки выяснилось, что есть такое ограничение, как регистр имени пользователя в Linux, описанное как
User/group names must match [a-z_][a-z0-9_-]*[$],
то есть можно и нужно было посмотреть в тот же chkname.c., и увидеть там
* is_valid_user_name(), is_valid_group_name() - check the new user/group
можно, и даже пароль ему поставить можно, но потом разбираться с chmod\chown для \home\ Ansible1\.ssh – это какой-то совсем не очевидный процесс. Хотя при входе с паролем такое имя пользователя работает, но разбираться еще и с этим в рамках статьи было лень, и, значит, пусть все будет строчное.
username=ansible1
/usr/sbin/useradd -m -s /bin/bash "$username"
# как не надо изобретать свои костыли
# Superrandom1=$RANDOM*$RANDOM+$RANDOM-$RANDOM*$RANDOM
# это строка, и математика так не сработает. Это и не важно
# PASS1=$(echo -n $Superrandom1 | sha256sum)
# echo $PASS > ps1.txt
# echo ${PASS:0:64} > ps2.txt
# PASS2=`date`
# PASS3=${PASS2:0:3}-${PASS:0:60}
#PASS2=${PASS:0:61}
# echo -e $PASS2\n$PASS2 | passwd "$username"
# так не работает, потому что в sha256sum попадаются простые сочетания.# echo $PASS3
# echo $username:$PASS3 | /usr/sbin/chpasswd #тут почему-то denied
PASS4=`cat /dev/urandom | fold -w 32 | head -n 1`
echo $username:$PASS4 | /usr/sbin/chpasswd
Пароль поставили. Но он не нужен, потому что вход будет по сертификату. Пока переделывал оформление, вспомнил, что не отключил вход по паролю.
Добавлю группу и юзера в группу
groupname=ans_demo1
/usr/sbin/groupadd $groupname
/usr/sbin/usermod --append --groups $groupname $username
Теперь sudoers для группы:
mkdir -p /etc/sudoers.d
curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/ansible/raw?ref=main -o /etc/sudoers.d/ansible
где 2 – ID проекта, а токен ваш токен. Токен в примере выше я сменил, и это токен из домашней лабы, и с этим токеном не так много можно увидеть даже в домашней лабе. Можно увидеть открытую часть ключа, и метод генерации случайного пароля.
Содержание sudoers.d/ansible
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
## Allows people in group wheel to run all commands
%ans_demo1 ALL=(ALL) ALL
## Same thing without a password
%ans_demo1 ALL=(ALL) NOPASSWD: ALL
и остается сделать только
curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/authorized_keys/raw?ref=main -o /home/$username/.ssh/authorized_keys --create-dirs
Думали все так просто? Ничего подобного. Так работать без дополнительной магии не будет.
4.4 Отлаживаем SSH, снова и опять
Эти пара абзацев написаны после того, как в разделе 4.9 ничего не заработало, потому что я попросту забыл настроить права на файл. Вроде, столько раз делал, и опять забыл.
По результатам перегенерации всех паролей и настроек я имею:
Локально, на ПК с Windows,
Было ex1_public.pkb, стало pikabu_part4_public – файл с открытым ключем. Расширение значения не имеет
Было ex1_private.ppk, стало pikabu_part4_private.ppk - файл с закрытой частью ключа.
Это два текстовых файла. Конечно, лучше бы файл с закрытой частью ключа хранить на зашифрованном разделе, или в волте, или как-то еще, «закрыто».
Файл pikabu_part4_export4ssh. Это экспорт файла для ssh, потом будет нужен.
В гите, в файле authorized_keys, лежит, в одну строку, длинная строка с началом
ssh-rsa AAAAB… и окончанием == и какой-то комментарий, который вы сами пропишете при создании ключа.
Поскольку дело происходит в Debian, то речь про
tail -f /var/log/auth.log – не идет, а в
journalctl -ru ssh | grep 'Could not open user' -
будет
Could not open user 'Ansible1' authorized keys '/home/Ansible1/.ssh/authorized_keys': Permission denied
В интернетах пишут: сделайте chmod да chown, и будет вам радость да счастье. Возьму да сделаю!
chmod 600 /home/ansible1/.ssh/authorized_keys
chmod 700 /home/ansible1/.ssh/
chown ansible1 /home/ansible1/.ssh/ -R
systemctl restart ssh.service
Про вот такое еще пишут знающие люди в интернетах, но я пожалуй, воздержусь.
chown ansible1:ans_demo1 /home/ansible1/.ssh –R
И вот замечу я, что когда все буковки в имени пользователя стали маленькие, то волшебным образом все заработало. А пока user был Ansible1, то есть с большой буквы, то сделав
все работало, а через ssh – нет. И магия это мне не понятна, поскольку и ключи я переделал, и все переписал, и три раза перегрузил, а ишь ты – не работало, и хоть ты что делай.
Потому делал я всякое непотребное,
pkill -KILL -u ansible1
и
/usr/sbin/deluser ansible1
4.5 Проблемы с переносом строки и не только
Если вы копипастите из винды \ текстового файла (в формате винды с переносом строки и возвратом каретки), и потом тащите этот файл в git, и потом на Linux машину, не забудьте сменить формат переноса строки через
sed -i 's/\r$//' filename
Потому что иногда так получается, что скрипт в две строки, из примера в третьей части, работает нормально. Вот такой еще работает,
А что-то длиннее начинает ругаться на
command not found
или
unexpected token `newline
Прям печаль и беда. Что остается? Костыли.
curl --header "PRIVATE-TOKEN: glpat-norDQhvwoTxyAtM9ANhV" http://192.168.1111.2222/api/v4/projects/2/repository/files/second.sh/raw?ref=main -o ansible4.txt && sed -i 's/\r$//' ansible4.txt && bash ansible4.txt
Конечно, правильнее хранить токены не в коде, а в vault. Но если у этого токена доступ только к этому проекту, то .. и так сойдет.
И, конечно токен не стоит хранить в коде, а имеет смысл передавать как параметр в командной строке. Или нет.
4.6 Проверяем, что получилось
Во первых. Оказалось, что я забыл установить пакет sudo. Нет его в Debian12 и Debian 13 by Proxmox. Не забудьте дописать
Во вторых. Оказалось, что я на одном из хостов не прописал Nexus в /etc/apt/sources.list, отчего обновления не шли. Тоже не забывайте.
В третьих. На одном хосте словил: bash: curl: command not found
Потому что curl там лежит, почему-то, только в:
/usr/share/bash-completion/completions/curl
/usr/lib/python3/dist-packages/curl
как это в одном дебиане работает, в другом нет, я даже не пытаюсь понять. Поэтому:
сначала nano /etc/resolv.conf (где у меня с времен тестов SSSD прописан DNS от AD DC), потом
apt install curl -y
apt install sudo -y
заменить или исправить /etc/resolv.conf
заменить или исправить /etc/apt/sources.list. Причем, при использовании Nexus, это будут разные sources.list.
Или нет ? Прописать то я могу что угодно.
Отредактировать /etc/ssh/sshd_config – для входа только по сертификату, сделать PasswordAuthentication no
Вопрос «что лучше, исправить или заменить» - мне не ясен. И та и другая операция приводит к одному виду, а что «правильнее» ?
В скрипте выше в строке
PASS4=`cat /dev/urandom | fold -w 32 | head -n 1`
возникают ошибки
ansible4.txt: line 17: warning: command substitution: ignored null byte in input
Да и путь -o ansible4.txt правильнее переписать как -o ~/ansible4.txt, но опять же, если это все работает не от рута, потому что у рута домашняя директория (~) вовсе не /home/user.
И что с этим делать, мне тоже не понятно. То есть, мне то понятно, ничего не делать.
Осталось удалить созданную в ходе опытов директорию ''$'\r'?. Оказалось, что проще всего ее сначала переименовать, иначе удаление делает некоторые фокусы, особенно если сделать rm $'*
Еще от влияния переноса строки образовалась не только \home\Ansible1, но и:
'Ansible1'$'\r' и 'Ansible1'$'\r\r'
И в скрипт можно бы и дописать, rm ansible5.txt.
Или даже и apt update && apt upgrade -y, вдруг забыл кто-то, кто систему ставил, сделать сразу же после настройки /etc/apt/sources.list.
4.7 Почти переходим к второму Ansible плейбуку
Вначале было слово, потом был вообще не плейбук, а команда
ansible proxmox -m setup -a "filter=ansible_uptime_seconds" --ask-pass
Первый плейбук, /home/user/uptime_report.yml, описан в первой статье, как и перечень хостов к нему,/home/user/1st_hosts.ini.
Итоговая команда была:
ansible-playbook uptime_report.yml --ask-pass --user root --inventory /home/user/1st_hosts.ini
При этом, я еще и умудрился файл uptime_report.yml сломать. Точнее, случайно удалить, и при переписывании получить:
[ERROR]: YAML parsing failed: This may be an issue with missing quotes around a template block.
Origin: /home/user/uptime_report.yml:11:19
9 msg:
10 Hostname: {{ inventory_hostname }}
11 Uptime: {{ (ansible_uptime_seconds / 86400) | int }} days,
^ column 19
Вот она, проблема vibe кода. Какой запрос был, я, конечно, не помню. Почему сломалось – не знаю, и написано не понять. Починить? Могу, но.
Но я так люблю возню с пробелами, кавычками, и даже со скобками, вы не представляете насколько.
4.8 Воюем с пробелами в Anisble и восстанавливаем пример из первой статьи
В статье Slurm: Примеры Ansible-Playbook есть максимально простой пример (пробелы в начале строки заменены на ! для лучшей считаемости числа пробелов). Файл: example01.yml
- hosts: all
!!tasks:
!!!!- name: Проверить доступность
!!!!!!ping:
Такой сценарий работает, и ему, кстати, безразлична возня с переносами строки.
---
- name: Print the gateway for each host when defined
ansible.builtin.debug:
msg: System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}
when: ansible_default_ipv4.gateway is defined
То я получу новую ошибку:
[ERROR]: 'ansible.builtin.debug' is not a valid attribute for a Play
То есть, пример Simple Ansible playbook to get the Linux host uptime – работает, а пример из документации – не работает, то есть не совсем не работает, но это очередной случай «в документации забыли прописать очевидное тем, кто ее пишет, и не очевидное тем, кто ее читает первый раз».
Пример 2 (файл: example2) «Не работает!!!!!111111». Пробелы заменены на 1234.
Ошибка: [ERROR]: YAML parsing failed: Colons in unquoted values must be followed by a non-space character.
- name: Print the gateway for each host when defined
12hosts: all
12gather_facts: true # Ensure facts are gathered to get ansible_uptime_seconds
12tasks:
12- name: Display uptime for each host
123456debug:
12345678msg: " Examlpe test 2 "
123456when: (uptime_days | int) > 30
Пример 3 (файл: example3) так работает проверка синтаксиса, но не исполнение yml. Пробелы заменены на 1234
- name: Print the gateway for each host when defined
12hosts: all
12gather_facts: true # Ensure facts are gathered to get ansible_uptime_seconds
12tasks:
1234- name: Display uptime for each host
123456debug:
12345678msg: " Examlpe test 3 "
123456when: (uptime_days | int) > 30
Как легко заметить (не легко, но можно):
Не работающий пример:
12- name: Display uptime for each host
Пример с работающей проверкой синтаксиса:
1234- name: Display uptime for each host
Пример 3 (выше), хотя и проходит проверку, но не будет работать с ошибкой:
[ERROR]: Task failed: Error while evaluating conditional: 'uptime_days' is undefined
Пример «НЕ Работает #21 ». Пробелы заменены на 1234
Точнее, не проходит проверку синтаксиса командой
ansible-playbook --syntax-check report21.yml, с ошибкой
YAML parsing failed: This may be an issue with missing quotes around a template block.
---
- name: Get and display system uptimes 21
12hosts: all
12gather_facts: true # Ensure facts are gathered to get ansible_uptime_seconds
12tasks:
1234- name: Display uptime for each host
123456ansible.builtin.debug:
12345678msg:
1234567890Hostname: {{ inventory_hostname }}
1234567890Uptime: {{ (ansible_uptime_seconds / 86400) | int }} days,
1234567890{{ ((ansible_uptime_seconds % 86400) / 3600) | int }} hours,
1234567890{{ (((ansible_uptime_seconds % 86400) % 3600) / 60) | int }} minutes,
1234567890{{ (((ansible_uptime_seconds % 86400) % 3600) % 60) | int }} seconds
123456when: ansible_uptime_seconds is defined
Пример «Работает #22». Пробелы заменены на 1234
Это то, что генерит google AI для примеров, и оно даже работает. Пока не надо отлаживать.
---
- name: Show uptime for each host in seconds
12hosts: all
12gather_facts: true # Ensure facts are gathered
12tasks:
1234- name: Display uptime in seconds
123456ansible.builtin.debug:
12345678msg: "Host {{ inventory_hostname }} has been up for {{ ansible_uptime_seconds }} seconds."
Только картинкой, отличия от #21 очевидны – кавычки открыли, кавычки закрыли.
12345678msg:
и
12345678msg: "
4.9 Возвращаемся к основной задаче
Для следующего подхода нам потребуется два, а лучше три файла.
первый - second_inventory.ini
В комментариях мне в файл с настройками ткнули, все правильно сделали, но еще не время переходить к такой конфигурации.
Поэтому сначала:
cp /etc/ansible/hosts /home/user/second_inventory.ini.
Не забыв прочитать Ansible How to build your inventory, и вписав в конфигурацию секцию для закрытой части ssh ключа.
[ansible4]
192.168.1111.2222 ansible_user= ansible1
192.168.1111.3333 ansible_user= ansible1
192.168.1111.4444 ansible_user= ansible1
# compare with: files/second.sh
[all:vars]
ansible_python_interpreter=/usr/bin/python3
[defaults]
private_key_file = /home/ansible1/.ssh/ansible_key
Какой-то странный хардкод получается. Потому что при смене имени я должен прописать новое имя и в скрипте настройки, и в коде для Ansible. Если бы я вел документацию, то подумал бы, как сделать иначе, и прописал бы это где-то в ридми.
Не забываем, что закрытый ключ из Puttygen в Windows нужно экспортировать в другой формат. Как это сделать, знает даже AI,
To copy a private key generated by PuTTYgen for use with Ansible, which typically expects OpenSSH format keys, you need to convert it and then copy the resulting OpenSSH-formatted private key.
Steps to Convert and Copy the Private Key:
Open PuTTYgen: Launch the PuTTY Key Generator application.
Load the Private Key: Click the "Load" button and navigate to your existing private key file (usually with a .ppk extension) generated by PuTTYgen. Enter the passphrase if prompted.
Export as OpenSSH Key:
Go to the "Conversions" menu.
Select "Export OpenSSH key."
You will likely be prompted to save it without a passphrase; choose "Yes" if you want to use it without a password in Ansible (which is common for automated tasks).
Save the OpenSSH Key: Choose a location and filename for the exported OpenSSH-formatted private key (e.g., id_rsa or ansible_key). This file will not have a .ppk extension.
Copy the Key to your Ansible Control Machine: Transfer this newly exported OpenSSH private key file to your Ansible control machine. A common location for private keys on Linux-based systems is ~/.ssh/.
Set Permissions (on Ansible Control Machine): Ensure the private key file has appropriate permissions, typically read-only for the owner to prevent unauthorized access.
chmod 400 ~/.ssh/ansible_key
Не забываем настроить WinSCP,
Open WinSCP: and navigate to the main menu.
Select Options > Preferences.
In the Preferences dialog box, go to the Panels tab.
Locate the option "Show hidden files" and check the box next to it.
Click OK to apply the changes.
Работать так все равно не будет, если вы, конечно, не пошли от рута. Поэтому положим куда попало и переложим от рута локально
переложим файл pikabu_part4_export4ssh в /home/user и сделаем
mv pikabu_part4_export4ssh /home/ansible1/.ssh/ansible_key
Проверим что получилось.
ansible-playbook report23.yml --inventory /home/user/second_inventory.ini
Failed to parse inventory with 'ini' plugin: Failed to parse inventory: Expected key=value host variable assignment, got: Ansible1
Кто нашел ошибку сам – тот молодец.
кто не нашел, может сравнить
192.168.1111.2222 ansible_user= ansible1
192.168.1111.2222 ansible_user=ansible1
Отдельно надо сказать про ошибку с блоком [defaults]
[defaults]
private_key_file
потому что достаточно прочитать обсуждение ошибки private_key_file vs ansible_ssh_private_key_file #79186 , связанную с определение порядка переменных - Understanding variable precedence, но это уже другая история.
Поэтому, этот блок из конфигурации я пока удалю, и сделаю ручную проверку,
ansible-playbook report23.yml --inventory /home/user/1st_hosts.ini --user ansible1 --private-key /home/ansible1/.ssh/ansible_key
Которая не сработает, и поэтому переходить дальше, к команде
ansible-playbook report23.yml --inventory /home/user/second_inventory.ini --private-key /home/ansible1/.ssh/ansible_key
Потому что команда может и не сработает, с сообщением Server refused our key.
Потому что после копирования ключа, атрибуты на файл /.ssh/ansible_key никто не перепроверил.
Потому что все, что выше, было написано до появления раздела 4.4 Отлаживаем SSH, снова и опять
Потому что 192.168.1111.2222 настроен на порт 2424, а на порту 22 висит ssh до одного из контейнеров, и надо прописать не
192.168.1111.2222 ansible_user=ansible1
а
192.168.1111.2222 ansible_user=ansible1 ansible_port=2424
С чем еще пришлось столкнуться.
С недостатком памяти на VM. Я, в ходе опытов, снизил оперативную память VM с 5000 до 4750 мегабайт. Как оказалось, 4750 МАЛОВАТО.
В остальном поздравляю, вы великолепны, и можете сделать то, ради чего все затевалось, но про это будет уже в пятой части.
Тег Ansible не добавляется, ну что тут сделать.
@editors, как теги то добавлять??