🏠: expect

Автоматизация установки SSL-сертификата на Cisco ASA

Пересадить всю контору на сертификаты Let’s Encrypt оказалось не очень сложно — certbot на реверс-прокси работает, сертификаты после перевыпуска лепятся вместе со своими ключами и кладутся в нужный каталог скриптом, лежащим в /etc/letsencrypt/renewal-hooks/deploy, а в определённое время в cron срабатывает команда, которая при наличии новых сертификатов перечитывает конфигурацию сервера.

На некоторых сервисах, которые выставлены в интернет напрямую, например, сервера видеоконференций, работает свой бот и он обновляет сертификаты локально, но иногда есть нюансы — например, на сервере TrueConf 80-й порт, который требуется для обновления сертификата, занят, и приходится подкручивать /lib/systemd/system/certbot.timer на еженедельный запуск где-то в глухой ночи, а в юнит-файле /lib/systemd/system/certbot.service рисовать следующее, чтобы стопорить службу, занимающую порт, и после процедуры стартовать её заново:

ExecStart=/usr/bin/certbot -q renew \
--pre-hook 'systemctl stop trueconf-web' \
--deploy-hook 'cp /etc/letsencrypt/live/tconf.example.com/cert.pem /opt/trueconf/server/etc/webmanager/ssl/custom.crt && \
cp /etc/letsencrypt/live/tconf.example.com/privkey.pem /opt/trueconf/server/etc/webmanager/ssl/custom.key && \
chown trueconf:trueconf /opt/trueconf/server/etc/webmanager/ssl/custom.\*' \
--post-hook 'systemctl start trueconf-web'

Окно ввода логина и пароля VPN на веб-странице Cisco ASA

Последним бастионом оставался довольно почтенного возраста аппаратный шлюз Cisco ASA, который со всеми этими новомодными удостоверяющими центрами работать не умеет. Так как раньше покупался wildcard-сертификат на год и вставлялся туда руками, проблем его менять не было, кроме оскорбления здравого смысла при виде очередного рассовывания этого несчастного сертификата по всем серверам и раздумий, куда его ещё забыли скопировать. Но так как Let’s Encrypt выпускает сертификаты на 90 дней, ручная установка выглядит совсем уж неуместной, и автоматизация совершенно необходима.

Let’s Encrypt даёт возможность выпускать и wildcard, но для него нужно автоматизировать ещё и создание TXT-записей в DNS через API, что усложняло задачу. Для нашего nic.ru существует программа, но эта схема мне показалось чересчур усложнённой, к тому же, у компании не так много доменов, в случае компрометации сертификата не будет затронуто сразу всё, тем более, что в один сертификат можно поместить сразу несколько альтернативных имён (SAN), что позволяет сократить общее количество выпускаемых сертификатов.

После небольшого гуглежа я обнаружил в некотором роде бриллиант — инструмент для автоматизации работы с интерактивным вводом expect. То есть, пишешь, что должно появиться в командной строке, а потом то, что нужно ввести в ответ. Так как это не bash, а другой интерпретатор, основанный на языке TCL (Tool Command Language), у него свой шебанг — #!/usr/bin/expect -f.

Перед началом работы нужно попросить сетевиков, чтобы ASA пробрасывала 80-й порт на сервер, на котором будет стоять сертбот, потом нужно завести отдельную учётку для входа по SSH и дать этой учётке права на некоторые команды, об этом ниже.

На сервере, куда будет приходить 80-й порт и где будет происходить всё последующее, понадобятся 3 файла: скрипт сборки сертификата в нужный формат (pkcs12, закодированный в base64), запускаемый сертботом после его перевыпуска, который, в свою очередь, запускает скрипт expect. Третий файл — это пароль экспорта/импорта сертификата: можно пароль и так захардкодить в первые два файла, но это неудобно. Предполагается, что все действия делаются от учётки root.

# скрипт, выполняющийся при обновлении сертификата
touch /etc/letsencrypt/renewal-hooks/deploy/vpncert-asa.sh
chmod 700 /etc/letsencrypt/renewal-hooks/deploy/vpncert-asa.sh
# скрипт expect
touch /scripts/vpncert-install.exp
chmod 700 /scripts/vpncert-install.exp
# пароль экспорта сертификата для openssl
touch /scripts/vpncert-asa.txt
chmod 600 /scripts/vpncert-asa.txt
# Записать пароль в файл пароля
nano /scripts/vpncert-asa.txt

Содержимое vpncert-asa.sh:

#!/bin/bash

openssl pkcs12 -export \
-password file:/scripts/vpncert-asa.txt \
-in $RENEWED_LINEAGE/fullchain.pem \
-inkey $RENEWED_LINEAGE/privkey.pem \
-out /root/gate.pfx && \

openssl base64 -in /root/gate.pfx -out /root/gate.base64 && \

/scripts/vpncert-install.exp

vpncert-install.exp:

#!/usr/bin/expect -f

set timeout 5
set send_slow {10 .001}
set sshUser "sshuser"
set sshIP "192.168.1.254"
set sshPass "sshPass12345"
set exportPass [exec cat /scripts/vpncert-asa.txt]

spawn ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa -o StrictHostKeyChecking=no $sshUser@$sshIP
expect "password:"
send -- "$sshPass\r"
expect ">"
send -- "enable\r"
expect "Password:"
send -- "$sshPass\r"
expect "#"
send -- "configure terminal\r"
expect "(config)#"
send -- "no crypto ca trustpoint ca_le\r"
expect {
  "]:" {send -- "yes\r"; exp_continue}
  "(config)#"
}
send -- "crypto ca trustpoint ca_le\r"
expect "trustpoint)#"
send -- "enrollment terminal\r"

expect "#"
send -- "exit\r"
expect "(config)#"

send -- "crypto ca import ca_le pkcs12 $exportPass\r"
expect "itself:"
send -- [exec cat /root/gate.base64]\n
send -s "quit\r"

# % The CA cert is not self-signed.
# % Do you also want to create trustpoints for CAs higher in the hierarchy? [yes/no]:
# OR
# % You already have RSA or ECDSA keys named ca_le.
# % If you replace them, all device certs issued using these keys
# % will be removed.
# % Do you really want to replace them? [yes/no]:
expect {
  "]:" {send -- "yes\r"; exp_continue}
  "(config)#"
}

send -- "ssl trust-point ca_le outside\r"

expect "(config)#"
send -- "exit\r"
expect "#"
send -- "exit\r"
expect eof

После запуска всей конструкции скрипт изготавливает сертификат требуемого формата, лезет по SSH на железку, вводит все логины-пароли, вставляет полученный сертификат и делает всё прочее как если бы это делалось руками, но только быстро, точно и без забывчивости.

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

spawn ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa -o StrictHostKeyChecking=no sshuser@192.168.1.254
sshuser@192.168.1.254's password:
User sshuser logged in to gate
Logins over the last 91 days: 54. Last login: 11:39:24 MSK Jan 9 2023 from 192.168.1.100
Failed logins since the last login: 0.
Type help or '?' for a list of available commands.
gate> enable
Password: **********
gate# configure terminal
gate(config)# no crypto ca trustpoint ca_le
WARNING: Removing an enrolled trustpoint will destroy all
certificates received from the related Certificate Authority.

Are you sure you want to do this? [yes/no]: yes
INFO: Be sure to ask the CA administrator to revoke your certificates.
gate(config)# crypto ca trustpoint ca_le
gate(config-ca-trustpoint)# enrollment terminal
gate(config-ca-trustpoint)# exit
gate(config)# crypto ca import ca_le pkcs12 verySecretPassword12345

Enter the base 64 encoded pkcs12.
End with the word "quit" on a line by itself:
MIIWzwIBAzCCFoUGCSqGSIb3DQEHAaCCFnYEghZyMIIWbjCCEOIGCSqGSIb3DQEH
BqCCENMwghDPAgEAMIIQyAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG
...
dt/zTsIeIqQq05PLFpOTIzBBMDEwDQYJYIZIAWUDBAIBBQAEIMyVqqTQhaaqlHOH
D3XnctPJR1TYytiVRCaVWuZHz+G0BAiEKmqw9Y6C4AICCAA=
quit
% You already have RSA or ECDSA keys named ca_le.
% If you replace them, all device certs issued using these keys
% will be removed.
% Do you really want to replace them? [yes/no]: yes

Trustpoint 'ca_le' is a subordinate CA and holds a non self-signed certificate.

Trustpoint CA certificate accepted.
WARNING: CA certificates can be used to validate VPN connections,
by default.  Please adjust the validation-usage of this
trustpoint to limit the validation scope, if necessary.
INFO: Import PKCS12 operation completed successfully.
gate(config)# ssl trust-point ca_le outside
gate(config)# exit
gate# exit

Logoff

Connection to 192.168.1.254 closed by remote host.
Connection to 192.168.1.254 closed.

Результат — компьютеры занимаются рутиной, а человек освободил время для чего-то более интересного.