ansible-collections / community.routeros

Ansible modules for managing MikroTik RouterOS instances.
https://galaxy.ansible.com/ui/repo/published/community/routeros/
GNU General Public License v3.0
93 stars 42 forks source link

New task module for configuring #33

Open jplitza opened 3 years ago

jplitza commented 3 years ago
SUMMARY

Add a new task module that makes it easier to idempotently modify RouterOS config.

ISSUE TYPE
COMPONENT NAME
ADDITIONAL INFORMATION

Currently, it's a huge PITA to idemtpotently modify RouterOS config. My roles have RouterOS script snippets like this all over the place, to be executed via CLI:

:if ([/ip firewall filter find chain=forward action=fasttrack-connection] = "") do={/ip firewall filter add chain=forward action=fasttrack-connection connection-state=established,related ipsec-policy=out,none} else={/ip firewall filter set [find chain=forward action=fasttrack-connection] ipsec-policy=out,none}

This looks for a firewall rule in the forward chain with action "fasttrack-connection". If it finds such a rule, it sets "ipsec-policy=out,none" on it. If it doesn't find such a rule, it adds one.

Obviously it's quite impossible to catch all edge cases (especially ordering etc.), because RouterOS config is damn complex. But I'd imagine something like this:

- community.routeros.config:
    path: /ip firewall filter
    attributes:
      chain: forward
      action: fasttrack-connection
      connection-state: established,related
      ipsec-policy: out,none
    match:
      - chain
      - action

One could also think about adding more parameters, like "place-before" (ideally accepting something like [find ...]), which would obviously only be used for adding, not for updating.

Also, this would make check mode and proper change detection possible.

Or is there a more elegant way to do this?

felixfontein commented 3 years ago

Currently, it's a huge PITA to idemtpotently modify RouterOS config.

I definitely agree on that. This collection currentl only provides basic modules for talking with RouterOS devices, and nothing more fancy.

I'm not familiar with other networking collections, but I know that people did a lot of work for solving such things. So it's probably worth to investigate what other networking collections do before starting to implement something new :) AFAIK resource modules are currently a big thing. (Unfortunately I forgot the little I knew about what they are...)

jplitza commented 3 years ago

I'm not familiar with other networking collections, but I know that people did a lot of work for solving such things. So it's probably worth to investigate what other networking collections do before starting to implement something new :) AFAIK resource modules are currently a big thing. (Unfortunately I forgot the little I knew about what they are...)

Okay, so I'll just dump more of my thoughts about this approach:

But you are right, there are other platforms without "commit". Cisco IOS comes to my mind, and indeed they have a bunch of resource modules instead of just one. The generic ios_config modules faces some of the above problems, too (see the example "load new acl into device", where they compare a given sequence of lines to the config dump, and if it's not found they simply delete the whole acl if present and load a new one.) So then there's the ios_acls module for that use case, too, and god it's convoluted. When you look at the first example, you see a no 10 command in between, followed by something that is based on the rule that was already there. This indicates that the module parses the previously present configuration from show running-config and dynamically generates the necessary commands to merge that configuration (if desired by the user). I'm shivering while imagining to parse RouterOS config exports on that level inside Ansible modules.

So yes, having resource modules for every path in the RouterOS config would be ideal (from a user perspective). A routeros_firewall module could replace whole chains of firewall rules with the above technique (maybe with some tricks like set comment="obsolete" [find chain=...]; add ...; rem [find comment=obsolete]). And it would most closely match overall Ansible behavior, whereas my proposal is still quite close to hacking something together on the CLI.

However, I fear this would also require a ridiculous amount of work to write all these modules for this seemingly small community. Then again, maybe it's all a question of smart "templating" of these resource modules. And starting with one (e.g. routeros_firewall) might show how difficult it actually is to implement them.

NikolayDachev commented 3 years ago

Изходен текст I have the same idea to implement scripts via api module before a long time however the limitation is in ros api and I was not sure if is even possible, will try to find a time for more research

note: set can be use via "cmd"

also check: https://galaxy.ansible.com/nikolaydachev/routeros_api in this collection I do a bunch of examples including firewall (check firewall role README)

jplitza commented 2 years ago

I looked a bit more at the resource modules. They are generated/scaffolded by the resource_module_builder. There's also a cli_rm_builder, but they assume a CLI which is probably not what we want: The resource modules work by first fetching the existing configuration of some resource (like "firewall") as facts, comparing them to the wanted configuration and generate commands to migrate from "have" to "want".

This approach might work for RouterOS to some extent (e.g. interfaces, IP addresses), since the RouterOS API gives us nicely formatted JSON data for each such resource. But I haven't seen a good handling of network resources yet where order is relevant. Other network OS have numbers in such cases, like Cisco ACLs or EdgeOS firewall rules. fortios_firewall_policy also don't have explicit ordering, and they solved this via some special "move" command with internal IDs returned by earlier invocations. This could be viable for RouterOS, too, but they apparently don't use the aforementioned resource_module_builder. Also, FortiOS too uses a HTTP API instead of SSH (like iOS and others).

If I'm not mistaken, the first step would be to write a httpapi plugin, analogous to the cliconf plugin we already have. This would make using the API feel more native to Ansible, using the usual host, username and password information instead of module arguments.

But I'm also feeling like there is almost 0 code in this collection that could be reused for this whole adventure.

jplitza commented 2 years ago

To ease the pain at hand, I wrote a role that implements an API in the spirit of what I outlined in my initial post.

felixfontein commented 2 years ago

At least some of this should be solved by #91.

jplitza commented 2 years ago

Guess I'll finally have to deal with deploying certificates from a self-hosted CA to all Mikrotiks in order to use the API without transmitting the password in cleartext.

felixfontein commented 2 years ago

Fortunately Ansible can help with that (community.crypto has all tools to create the PKI, and https://docs.ansible.com/ansible/latest/collections/community/routeros/docsite/api-guide.html#installing-a-certificate-on-a-mikrotik-router shows how to install the certificate with SSH access to the router).