summaryrefslogtreecommitdiff
path: root/plugins/modules/vyos_firewall_interfaces.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/modules/vyos_firewall_interfaces.py')
-rw-r--r--plugins/modules/vyos_firewall_interfaces.py244
1 files changed, 244 insertions, 0 deletions
diff --git a/plugins/modules/vyos_firewall_interfaces.py b/plugins/modules/vyos_firewall_interfaces.py
new file mode 100644
index 0000000..d9085a6
--- /dev/null
+++ b/plugins/modules/vyos_firewall_interfaces.py
@@ -0,0 +1,244 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_firewall_interfaces
+short_description: Firewall interfaces resource module via REST API.
+description:
+ - Attaches or detaches named firewall rule sets to/from interface
+ directions (in/out/local) using the VyOS HTTPS REST API.
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: List of interface firewall attachment configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Interface name.
+ type: str
+ required: true
+ access_rules:
+ description: Firewall rule sets per AFI.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address family.
+ type: str
+ choices: [ipv4, ipv6]
+ required: true
+ rules:
+ description: Rule sets and directions.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Rule set name.
+ type: str
+ direction:
+ description: Traffic direction.
+ type: str
+ choices: [in, local, out]
+ required: true
+ state:
+ description: Desired state.
+ type: str
+ choices: [merged, replaced, overridden, deleted, gathered]
+ default: merged
+ hostname:
+ type: str
+ required: true
+ port:
+ type: int
+ default: 443
+ api_key:
+ type: str
+ required: true
+ no_log: true
+ timeout:
+ type: int
+ default: 30
+ verify_ssl:
+ type: bool
+ default: false
+"""
+
+RETURN = r"""
+before:
+ returned: always
+ type: list
+after:
+ returned: when changed
+ type: list
+commands:
+ returned: always
+ type: list
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import (
+ VYOS_REST_CONNECTION_ARGSPEC,
+ VyOSRestClient,
+ VyOSRestError,
+)
+
+
+_IFACE_TYPES = {
+ "eth": "ethernet",
+ "bond": "bonding",
+ "vti": "vti",
+ "vxlan": "vxlan",
+ "lo": "loopback",
+ "dummy": "dummy",
+ "br": "bridge",
+ "wg": "wireguard",
+ "tun": "tunnel",
+}
+
+
+def _itype(name):
+ for p, t in _IFACE_TYPES.items():
+ if name.startswith(p):
+ return t
+ return "ethernet"
+
+
+def _fw_direction_key(afi, direction):
+ if afi == "ipv4":
+ return {"in": "in", "out": "out", "local": "local"}[direction]
+ return {"in": "in", "out": "out", "local": "local"}[direction]
+
+
+def _apply(client, iface_cfg, commands):
+ name = iface_cfg["name"]
+ itype = _itype(name)
+ ibase = ["interfaces", itype, name, "firewall"]
+
+ for ar in iface_cfg.get("access_rules") or []:
+ afi = ar["afi"]
+ fw_key = "firewall" if afi == "ipv4" else "ipv6"
+ for rule in ar.get("rules") or []:
+ direction = rule["direction"]
+ rs_name = rule.get("name")
+ if rs_name:
+ path = ibase + [direction, fw_key if afi == "ipv6" else "name"]
+ client.configure_set(path, rs_name)
+ commands.append(
+ "set interfaces {t} {n} firewall {d} {k} '{rs}'".format(
+ t=itype,
+ n=name,
+ d=direction,
+ k="ipv6-name" if afi == "ipv6" else "name",
+ rs=rs_name,
+ ),
+ )
+
+
+def _delete_iface_fw(client, name, commands):
+ itype = _itype(name)
+ path = ["interfaces", itype, name, "firewall"]
+ try:
+ client.configure_delete(path)
+ commands.append("delete interfaces {t} {n} firewall".format(t=itype, n=name))
+ except VyOSRestError:
+ pass
+
+
+def _get(client):
+ try:
+ result = client.retrieve_show_config(["interfaces"])
+ raw = result.get("data") or {}
+ out = []
+ for itype, itype_data in raw.items():
+ if not isinstance(itype_data, dict):
+ continue
+ for iname, idata in itype_data.items():
+ fw = idata.get("firewall") if isinstance(idata, dict) else None
+ if fw:
+ out.append({"name": iname, "firewall": fw})
+ return out
+ except VyOSRestError:
+ return []
+
+
+def main():
+ argument_spec = dict(
+ config=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str", required=True),
+ access_rules=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]),
+ rules=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str"),
+ direction=dict(
+ type="str",
+ required=True,
+ choices=["in", "local", "out"],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ state=dict(
+ type="str",
+ default="merged",
+ choices=["merged", "replaced", "overridden", "deleted", "gathered"],
+ ),
+ )
+ argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC)
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ client = VyOSRestClient(module)
+ state = module.params["state"]
+ config = module.params.get("config") or []
+ commands = []
+ changed = False
+ before = _get(client)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=before, before=before, commands=[])
+ if module.check_mode:
+ module.exit_json(changed=True, before=before, commands=["(check mode)"])
+
+ try:
+ if state == "deleted":
+ targets = {i["name"] for i in config} if config else {i["name"] for i in before}
+ for iface in before:
+ if iface["name"] in targets:
+ _delete_iface_fw(client, iface["name"], commands)
+ changed = True
+ elif state in ("merged", "replaced", "overridden"):
+ if state in ("replaced", "overridden"):
+ for cfg in config:
+ _delete_iface_fw(client, cfg["name"], commands)
+ for cfg in config:
+ _apply(client, cfg, commands)
+ changed = True
+ except VyOSRestError as exc:
+ module.fail_json(msg=str(exc))
+
+ after = _get(client) if changed else before
+ module.exit_json(changed=changed, before=before, after=after, commands=commands)
+
+
+if __name__ == "__main__":
+ main()