Mikrotik и ansible: устанавливаем сертификаты

У Mikrotik, как и у каждого уважающего себя вендора, есть API для работы со своими продуктами. Cтоит признать, этот API даже весьма рабочий.
Я, в своё время, успел поработать с perl и python реализациями этого API, но процессе написания статей из цикла “Hotspot для самых маленьких” появилось стойкое убеждение, что, в большинстве случаев, это всё то же забивание консольной команды и получение результата в какой-нибудь удобоваримой форме (хотя и есть, вроде, ООП-реализации, но и они далеки от идеала). Плюс само API несёт некоторые ограничения (например, нельзя с его помощью вызывать команды встроенного интерпретатора RouterOS).

Однако же, далеко не всегда нужно использовать API. Например, если нужно просто выполнить определённую последовательность действий (пусть и параметризованную), гораздо быстрее, удобнее и надёжнее использовать ansbile (как я уже показал в прошлой статье).

А тут как раз подвернулась задачка – нужно раз в три месяца обновлять сертификаты на нашем микротике, которые мы получаем от let’s encrypt.
Хотите узнать, как? Прошу под кат 😉


Для начала определимся, что у нас уже есть микротик, debian и certbot, через который мы и получаем сертификаты от let’s encrypt. Вместо certbot можно использовать любое другое, удобное для вас решение. Так же подразумевается, что вы уже получили сертификат любым удобным вам образом (например через webroot).

Т.к. нативного модуля для Mikrotik у Ansible нет, то приходится выкручиваться. Один из способов – это выполнение команд на устройстве последовательно через ssh. Можно было бы, конечно, залить rsc-файл и сделать import, но это лишает нас возможности получить хоть какую-то отладочную информацию.
На эту идею меня натолкнул пользователь 0x566164696D, за что ему большое спасибо.

ansible.cfg будет примерно таким:

[defaults]
inventory = inventory
remote_tmp = $HOME/.ansible/tmp
pattern = *
forks = 20
poll_interval = 15
transport = smart
remote_port = 22
roles_path = ./roles
host_key_checking = False
sudo_exe = sudo
timeout = 10
remote_user = hspot_admin
ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}
action_plugins = /usr/share/ansible_plugins/action_plugins
callback_plugins = /usr/share/ansible_plugins/callback_plugins
connection_plugins = /usr/share/ansible_plugins/connection_plugins
lookup_plugins = /usr/share/ansible_plugins/lookup_plugins
vars_plugins = /usr/share/ansible_plugins/vars_plugins
filter_plugins = /usr/share/ansible_plugins/filter_plugins
etcd_url = 'http://127.0.0.1:4001'
retry_files_save_path = ~/ansible/.ansible-retry

Особое внимание обратите на remote_user (его мы создадим на микротике и roles_path – в этой папке будут лежать наши роли.

Сначала зададим переменные:

rsa_key_file: "ansible_user.key"
mkt_user_name: "hspot_admin"
mkt_user_key: "{{ lookup('file', rsa_key_file) }}"
cert_dir: "/etc/letsencrypt/live/oauth.{{ hspot_domain }}/"
cert_file: cert.pem
key_file: privkey.pem
chain_file: fullchain.pem

в ansible_user.key лежит мой публичный ssh-ключ.

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

#file=tasks/main.yml
---
- name: Generate random password via pwgen
command: "pwgen -s 19 1"
register: random_passwd

- name: Generate .rsc to check and add user
template: src=add_ansible_user.rsc.j2 dest=/tmp/{{inventory_hostname}}.rsc

- name: Run .rsc script
command: bash -c "cat /tmp/{{inventory_hostname}}.rsc | sshpass -p "{{ros_passwd}}" ssh {{ros_login}}@{{ansible_host}} -p {{ansible_ssh_port}} -T -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=1"

- name: Delete temp .rsc file
file: path=/tmp/{{inventory_hostname}}.rsc state=absent

#file=templates/add_ansible_user.rsc.j2
:if ([/user find name={{mkt_user_name}}] ="") do={ :log info "User {{mkt_user_name}} not found... Add it"}
:if ([/user find name={{mkt_user_name}}] ="") do={/file print file=ansible_key.txt; :delay 2; /file set ansible_key.txt contents="{{mkt_user_key}}";}
:if ([/user find name={{mkt_user_name}}] ="") do={/user add name={{mkt_user_name}} group=full password="{{random_passwd.stdout}}"; :delay 2; /user ssh-keys import user={{mkt_user_name}} public-key-file=ansible_key.txt}

Прогоняем плейбук. Если всё отработало штатно, то у нас на микротике будет наш пользователь:

PLAY [all] *********************************************************************

TASK [add-to-ansible : Generate random password via pwgen] *********************
changed: [hotspot]

TASK [add-to-ansible : Generate .rsc to check and add user] ********************
changed: [hotspot]

TASK [add-to-ansible : Run .rsc script] ****************************************
changed: [hotspot]

TASK [add-to-ansible : Delete temp .rsc file] **********************************
changed: [hotspot]

PLAY RECAP *********************************************************************
hotspot : ok=4 changed=4 unreachable=0 failed=0

Теперь, наконец, можно приступить к заливке сертификатов. Роль будет вот такая:

#file=tasks/main.yml
---
- name: Generate .rsc to add certs
template: src=cert_install.rsc.j2 dest=/tmp/{{inventory_hostname}}.rsc

- name: Copy certs to device
copy:
src: "{{cert_dir}}/{{item}}"
dest: /
with_items:
- "{{cert_file}}"
- "{{key_file}}"
- "{{chain_file}}"
- name: Activate certs on device
action: shell "cat /tmp/{{inventory_hostname}}.rsc | ssh -T {{inventory_hostname}} -p {{ansible_ssh_port}}"

- name: Delete temp .rsc file
file: path=/tmp/{{inventory_hostname}}.rsc state=absent

#file=templates/cert_install.rsc.j2
#1. Remove cert file with same common-name (You need to define it below)
/foreach i in=[/certificate find common-name="Let's Encrypt Authority X3"] do {/certificate remove $i}
/foreach i in=[/certificate find common-name="{{hspot_oauth}}"] do {/certificate remove $i}
#2. install new certs (cert.pem, privkey.pem, chain.pem)
/certificate import file={{cert_file}}
/certificate import file={{key_file}}
/certificate import file={{chain_file}}
#3. check www-ssl and hotspot certs (set new cert at www-ssl and hotspot you need to define common-name)
/foreach i in=[/ip hotspot profile find login-by=https] do {/ip hotspot profile set $i ssl-certificate=[/certificate find common-name={{hspot_oauth}}]}
/foreach i in=[/ip service find name=www-ssl] do {/ip service set $i certificate=[/certificate find common-name={{hspot_oauth}}]}

Запускаем наш плейбук:

PLAY [all-mkt] ****************************************************************

TASK: [cert_install | Generate .rsc to add certs] *****************************
ok: [10.0.2.7]

TASK: [cert_install | Copy certs to device] ***********************************
changed: [10.0.2.7] => (item=cert.pem)
changed: [10.0.2.7] => (item=privkey.pem)
changed: [10.0.2.7] => (item=chain.pem)

TASK: [cert_install | Activate certs on device] *******************************
changed: [10.0.2.7]

TASK: [cert_install | Delete temp .rsc file] **********************************
ok: [10.0.2.7]

PLAY RECAP ********************************************************************
10.0.2.7 : ok=4 changed=2 unreachable=0 failed=0

Если всё ок, то на микротике мы увидим свежие сертификаты.

В этой статье я разобрал довольно простой случай, поле для улучшения обширное, как и для написания других плейбуков, упрощающих работу с микротиками. К сожалению, для ansible ещё нет нативного модуля router-os, поэтому все операции выполняются через shell, что совсем не best way. Но, будем надеяться на лучшее, а пока просто лучше продумывать логику плейбуков, дабы повторный накат ничего не сломал.

Мой форк репо mikroansible можно найти на github. Там же лежат роли, описанные в этой статье.

2 Comments

  1. У меня никак не получается подключиться энсиблом к микротика по ключу.
    Ключ работает, микротик пускает, но через энсибл пишет: “not a valid RSA private key file”
    Не знаете в чём может быть причина?
    ansible_connection : network_cli
    ansible_user : user
    ansible_ssh_private_key_file : /home/user/.ssh/lin_rsa
    ansible_network_os : routeros
    ansible_become : yes
    ansible_become_method : enable

    и playbook
    – name: Test connection
    hosts: all

    tasks:

    – name: test
    routeros_command:
    commands: /system resource print
    Линукс и вин машины опрашивает без проблем

    1. Дело в том, что для Ansible (из-за особенностей Python) ключ должен быть в pem формате. Для этого укажите -m pem

      ssh-keygen -m pem -t rsa -b 8192 -E sha512 -C ” -N ” -f keyfilename

Leave a Reply