Ansible Playbook for Updating Mikrotik RouterOS

This playbook is for updating Mikrotik routers. It will update both the RouterOS version and the firmware.

The playbook executes in the following order.

  1. Check for RouterOS Updates
  2. Update RouterOS (Router will reboot if there is an update)
  3. Sleep 120 seconds to allow the router(s) to boot up
  4. Check current firmware version, and if there is an available upgrade
  5. Update firmware
  6. Reboot router to apply firmware upgrade

This playbook attempts to be smart and will not reboot a router if there is not an update available. Routers that have updates available will reboot twice. Once to apply the RouterOS version, and the second time to apply the firmware.

Prerequisites

You should already have an inventory file and the Ansible RouterOS collection installed. If not, check out the following post.

Setup Ansible host file and RouterOS collection

Playbook

Here is the playbook.
A quick command syntax note, RouterOS 7 and newer typically use slashes / between commands. i.e. /system/package/update/install. Older versions of RouterOS have spaces in the command path i.e. /system package update install Since this still works on newer versions, we use it here.

---
- name: Mikrotik RouterOS and Firmware Upgrades
  hosts: routers
  gather_facts: false
  tasks:

# Update RouterOS version.  Mikrotik update/install command automatically reboots the router
  - name: Check for RouterOS updates
    community.routeros.command:
      commands:
        - /system package update check-for-updates
    register: system_update_print

  - name: Update RouterOS version
    community.routeros.command:
      commands:
        - /system package update install
    when: system_update_print is not search('System is already up to date')

# Check if firmware needs an upgrade, upgrade and reboot.
  - name: Sleeping for 120 seconds.  Giving time for routers to reboot.
    ansible.builtin.wait_for:
      timeout: 120
    delegate_to: localhost
      
  - name: Check Current firmware
    community.routeros.command:
      commands:
        - ':put [/system routerboard get current-firmware]'
    register: firmware_current

  - name: Check Upgrade firmware 
    community.routeros.command:
      commands:
        - ':put [/system routerboard get upgrade-firmware]'
    register: firmware_upgrade

  - name: Upgrade firmware
    community.routeros.command:
      commands:
        - ':execute script="/system routerboard upgrade"'
    when: firmware_current != firmware_upgrade

  - name: Wait for firmware upgrade and then reboot
    community.routeros.command:
      commands:
        - /system routerboard print
    register: Reboot_Status
    until: "Reboot_Status is search(\"please reboot\")"
    notify:
      - Reboot Mikrotik
    retries: 3
    delay: 15
    when: firmware_current != firmware_upgrade

  handlers:
    - name : Reboot Mikrotik
      community.routeros.command:
        commands:
          - ':execute script="/system reboot"'

Run the playbook with

ansible-playbook -i routers.ini mikrotik_update.yaml

Change routers.ini to your router inventory.
mikrotik_update.yaml to whatever you end up calling the playbook.

Setup Ansible and Mikrotik RouterOS

https://docs.ansible.com/ansible/devel/collections/community/routeros/

https://github.com/ansible-collections/community.routeros

Install the RouterOS collection.

ansible-galaxy collection install community.routeros

Create inventory

vi inventory.ini
[routers]
mikrotik ansible_host=192.168.88.1

[routers:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=community.routeros.routeros
ansible_user=admin
ansible_ssh_pass=
ansible_ssh_port=22

If you are using a custom SSH port, be sure that ansible-pylibssh is installed.

pip install ansible-pylibssh

Simple Playbook

This simple playbook will print the system resources. Playbook is taken from here.

---
- name: RouterOS test with network_cli connection
  hosts: routers
  gather_facts: false
  tasks:

  - name: Gather system resources
    community.routeros.command:
      commands:
        - /system resource print
    register: system_resource_print

  - name: Show system resources
    debug:
      var: system_resource_print.stdout_lines

  - name: Gather facts
    community.routeros.facts:

  - name: Show a fact
    debug:
      msg: "First IP address: {{ ansible_net_all_ipv4_addresses[0] }}"

View Fiber SFP details in Mikoritk RouterOS

Quick and simple way to check the details on a fiber SFP on a Mikrotik router. Replace sfp1_name with the SFP name or leave out the name and select a number.

/interface ethernet monitor "sfp1_name"

Results

                      name: sfp1
                    status: link-ok
          auto-negotiation: done
               full-duplex: yes
           tx-flow-control: no
           rx-flow-control: no
               advertising: 
  link-partner-advertising: 
        sfp-module-present: yes
               sfp-rx-loss: no
                  sfp-type: SFP-or-SFP+
        sfp-connector-type: LC
       sfp-link-length-9um: 3000m
           sfp-vendor-name: UBNT
    sfp-vendor-part-number: UF-SM-1G-S
         sfp-vendor-serial: FL31F80285729
    sfp-manufacturing-date: 20-02-20
            sfp-wavelength: 1550.32nm
           sfp-temperature: 64C
        sfp-supply-voltage: 3.23V
       sfp-tx-bias-current: 30mA
              sfp-tx-power: -5.254dBm
              sfp-rx-power: -4.1dBm
           eeprom-checksum: good
                    eeprom: 0000: 01 02 03 00 00 00 00 00  00 00 00 .....

DNS Query “sl 0.0.0.0” in Mikrotik DNS Cache

Sounds like this could be from a potential scan. The record is useless as 0.0.0.0 doesn’t go to anything.

https://learn.microsoft.com/en-us/answers/questions/547092/what-is-the-sl(0)-in-dns-logs-as-host-name

We can block this type of behavior by blocking inbound DNS request. Change in-interface to your interface or change to an interface list.

ip firewall filter add chain=input protocol=6 dst-port=53 in-interface=ether1 action=drop
ip firewall filter add chain=input protocol=17 dst-port=53 in-interface=ether1 action=drop

How to Install Mikrotik RouterOS on VirtualBox


Note that there are a couple of limitations of using the Cloud Hosted Router (CHR). The main issue is that the default license doesn’t allow for more than 1Mbps on each interface.

https://help.mikrotik.com/docs/display/ROS/Cloud+Hosted+Router%2C+CHR#CloudHostedRouter,CHR-CHRLicensing

Download the VDI version of CHR from the Mikrotik downloads page.

https://mikrotik.com/download

Mikrotik has instructions for installing CHR in VirtualBox, so this post is more of just a summary.

https://wiki.mikrotik.com/wiki/Manual:CHR_VirtualBox_installation

  1. Create a VM with Type Linux, Version Other Linux (64-bit)
  2. While setting up the VM, select the VDI downloaded from Mikrotik as the virtual disk.
  3. Start the VM and login with admin for the username and nothing for the password.

The easiest way to spin up more vm’s to right click on the VM and Clone.

How to Create WireGuard Point-to-point Between Mikrotik Routers

We’ll create a tunnel between two Mikrotik RouterOS routers. Once we have the tunnel connected, we can then route traffic between them.

Note: You can add Preshared keys, but we don’t cover that in this post, just to keep things simple. Check out the following post if you want to add Preshared keys.

How to Create a Preshared Key for Wireguard

Here is how we will want our routers set up. The WireGuard PtP IP is the IP addresses used on both ends of the tunnel. The WAN IP is the IP of each Router. Local IP on Host B is setup to distribute DHCP.

Host A

WAN IP: 172.16.0.1
WireGuard PtP IP: 10.1.1.1/30

Host B

WAN IP: 10.0.0.2
WireGuard PtP IP: 10.1.1.2/30
Local IP: 192.168.0.1/24

We need Host A to be able to access Private IP’s (192.168.0.0/24) behind Host B.

We’ll pretend that the 172.16.0.1 address is a public IP, and Host B, is behind some sort of NAT network.

To create the Point-to-point, or PtP, we will create a WireGuard VPN tunnel, and then add routes from Host A to Host B.

For each Mikrotik we need to create a WireGuard interface, and then a peer. One of the peers needs a keep alive if we are behind a NAT.

Wireguard Setup Overview

Here is an overview screenshot of what our WireGuard settings will look like. Host A is on top, and Host B on the bottom. On the left are the WireGuard interfaces, and the right contains the Peers.

We copy the Public Key from the remote WireGuard interface, to the Public Key on the local Peer. I.e. The Host_B Peer contains Host_A’s Interface Public Key and vice verse

Host A

If you want to, you can use the WinBox GUI to setup and configure the router.

Create the WireGuard interface

 /interface/wireguard/add name=wireguard-Host_A disabled=no

Add IP address 10.1.1.1/30 to the newly created WireGuard Interface in /IP/Address

/ip/address/add address=10.1.1.1/30 interface=wireguard-Host_A disabled=no

Create WireGuard Peer, WireGuard -> Peers

  • Select the WireGuard interface,
  • In the Allowed Addresses, put 10.1.1.0/30 and 192.168.0.0/24*.
  • Finally, put in the Public Key from Host B.
    Note that we can’t do this until we create the WireGuard Interface on Host B, so you’ll need to come back for this step.
interface/wireguard/peers/add interface=wireguard-Host_A public-key=HOST_B_WG_PUBLIC_KEY allowed-address=10.1.1.0/30,192.168.0.0/24

Add route for 192.168.0.0/24 to point to 10.1.1.2

/ip/route/add dst-address=192.168.0.0/24 gateway=10.1.1.2

*The Allowed Address sets which addresses work on the other side of the tunnel. If we don’t specify 192.168.0.0/24, then we won’t be able to route to those addresses. If we don’t add 10.1.1.0/30, then our tunnel won’t work at all. Since we only need to route to the 192.168.0.0/24 network from the Host A side, we don’t need this IP range on Host B.

Host B

Create the WireGuard interface, WireGuard -> Add

 /interface/wireguard/add name=wireguard-Host_B disabled=no

Add IP address 10.1.1.2/30 to the newly created WireGuard Interface in /IP/Address

/ip/address/add address=10.1.1.2/30 interface=wireguard-Host_B disabled=no

Create a WireGuard Peer, WireGuard -> Peers

  • Select the WireGuard interface,
  • In the Allowed Addresses, put 10.1.1.0/30
  • Finally, put in the Public Key from Host A.
/interface/wireguard/peers/add interface=wireguard-Host_A public-key=HOST_A_WG_PUBLIC_KEY endpoint-address=172.16.0.1 endpoint-port=13231 allowed-address=10.1.1.0/30 persistent-keepalive=00:00:30

Conclusion

That should be it. Verify that there is a connection. From Host A, ping 192.168.0.1 or any other remote device.

Troubleshooting

Unfortunately, there appear to be some wonky bugs with WireGuard on RouterOS. It does appear to be getting better, but here are a couple things to check if the tunnel is not connecting.

  1. Verify that the Firewall is not blocking WireGuard. You can allow the WireGuard port in the Firewall.
  2. Try disabling and re-enabling the Interfaces and/or Peers
  3. Verify that all the routes for the PtP are in /ip/routes. If not, try manually adding the route (10.1.1.0/30) on the WireGuard interface on both routers.
  4. Add a keep alive if a router is behind a firewall/NAT.
  5. Reboot and or Upgrade the RouterOS version and firmware.

Hardening Mikrotik RouterOS

https://wiki.mikrotik.com/wiki/Manual:Securing_Your_Router

Things to harden

  • Delete default admin user
  • Disable unused services and whitelist IP’s
  • Secure SSH
  • DNS

Delete default admin user

Before deleting the default admin user, create your own user account.

/user/add name=MyUsername group=full password=mylongsecurepassword

Note: running /user/add will prompt you for the rest of the options.

Delete the default admin user with

/user remove admin

We want to delete the default admin user for two reasons. 1. There is no default password for this user. 2. It is a default username which means it will be targeted for brute force attacks.

Consider using the /users/groups for more granular control.

Disable unused services

In the following, we disabled all services except SSH and Winbox. We also limit access to those services only from private “RFC 1918” IP addresses. Customize as needed.

/ip service
set telnet disabled=yes
set ftp disabled=yes
set www disabled=yes
set www-ssl tls-version=only-1.2
set ssh address="set winbox address="192.168.0.0/16,172.16.0.0/12,10.0.0.0/8"
set api disabled=yes
set winbox address="set winbox address="192.168.0.0/16,172.16.0.0/12,10.0.0.0/8"
set api-ssl disabled=yes tls-version=only-1.2

for www-ssl and api-ssl, tls-version is not a required argument, but you may consider using it if you need the API or Webfig.

Secure SSH

/ip/ssh/set strong-crypto=yes allow-none-crypto=no always-allow-password-login=no host-key-size=4096

And regenerate the SSH host key. It will prompt for a [y/N], hit y to regenerate.

/ip/ssh/regenerate-host-key 

DNS

Unless your device is being used as a DNS resolver, it is best to disable the “Allow Remote Request”

ip dns/set allow-remote-requests=no

If you do need it enabled, then be sure to add some firewall rules to keep your router from being used in amplification attacks.

add action=drop chain=input dst-port=53 in-interface-list=WAN protocol=udp

You can configure interface lists in /interface/list or Interface -> Interface List in the gui

Or you can change to in-interface and specify the WAN interface directly. You could also set it to !LAN if you have a LAN interface list set up.

MikroTik RouterOS Privilege Escalation Exploit CVE-2023-37099

Mikrotik Recently patched CVE-2023-37099 which was a way someone with an admin account, could escalate to a “super admin”, or jail break a router.

It appears the technique has been around for about a year.

Affected versions: < 6.49.7

The good news is that someone would already have to have an account to elevate permissions. If your routers have been using strong passwords or SSH public/private keys and have internet management disabled, then you are probably fine.

https://github.com/MarginResearch/FOISted

https://vulncheck.com/blog/mikrotik-foisted-revisited

Simple OSPF between Mikrotik Routers

https://help.mikrotik.com/docs/display/ROS/OSPF

Setting up OSPF between Mikrotik routers is not too difficult. The following commands should work with RouterOS version 7+. Run these commands on each Mikrotik changing out the router-id.

Create a Loop-back interface

First it would be a good idea to create an loopback interface that will stay up. We’ll use this address as the router-id. This should be unique per router.

/interface/bridge/add name=loopback
/ip/address/add address=1.2.3.4 interface=loopback

Now lets setup OSPF.

Create OSPF Instance

First we’ll create the instance. Use the address from the above loopback address. Technically you can use whatever id you want as long as it is a 32 bit “address” and is unique.

/routing/ospf/instance/add name=default router-id=1.2.3.4

IMPORTANT NOTE: If this router is also the default gateway, you’ll need to specify the “originate-default=always” option to share the default gateway over OSPF to the other routers. You don’t have to do this if you don’t want to share the default route.

Create OSPF Area

Now we can create an OSPF area. For a simple OSPF setup, we’ll just use the default 0.0.0.0 area.

routing/ospf/area/ add name=default area-id=0.0.0.0 instance=default

Create Instance

Now we can add an instance. This is responsible for what networks get shared with OSPF. If you want to do all the addresses on the router, then use 0.0.0.0/0. If you only want to do specific networks, run an entry for every network, changing 0.0.0.0/0 to the network of interest.

/routing/ospf/interface-template/add networks=0.0.0.0/0 area=default

Wrapping Up

After that we can check to make sure things worked.

/routing/ospf/neighbor/print

You should see at a neighbor. It can take a little bit for the neighbors to show up.

You can also check the routes on the router.

/ip/route/print

OSPF has a default distance of 110, so checking the routes is a quick way to verify the routes are getting updated. Do note that if you have a static route in with a lower distance, that will take precedence over OSPF.

Mikrotik DHCP Server Lease Script Variables

The following variables are usable inside of the DHCP-Server script tab.

  • leaseBound – set to “1” if bound, otherwise set to “0”
  • leaseServerName – dhcp server name
  • leaseActMAC – active mac address
  • leaseActIP – active IP address
  • lease-hostname – client hostname
  • lease-options – array of received options

The DHCP server script runs every time there is a new lease, or a lease expires. Doesn’t look to run when static leases renew.

DHCP Server Scripts

https://wiki.mikrotik.com/wiki/Manual:IP/DHCP_Server#General