diff --git a/changelogs/fragments/cliconf.yml b/changelogs/fragments/cliconf.yml new file mode 100644 index 00000000..53c26ada --- /dev/null +++ b/changelogs/fragments/cliconf.yml @@ -0,0 +1,4 @@ +--- + +minor_changes: + - added `network_os_major_version` to facts diff --git a/changelogs/fragments/firewall_global_14.yml b/changelogs/fragments/firewall_global_14.yml new file mode 100644 index 00000000..269c3e5a --- /dev/null +++ b/changelogs/fragments/firewall_global_14.yml @@ -0,0 +1,7 @@ +--- +minor_changes: + - with 1.4+, use the the global keyword to define global firewall rules + - Fixed ipv6 route-redirects and tests + - Added support for input, output, and forward chains (1.4+) + - Fixed state-policy deletion (partial and full) + - Added support for log-level in state-policy (1.4+) diff --git a/changelogs/fragments/firewall_rules.yml b/changelogs/fragments/firewall_rules.yml new file mode 100644 index 00000000..7cd02a24 --- /dev/null +++ b/changelogs/fragments/firewall_rules.yml @@ -0,0 +1,8 @@ +--- +breaking_changes: + - firewall_rules - tcp.flags is now a list with an inversion flag to support 1.4+ firewall rules, but still supports 1.3- + +minor_changes: + - firewall_rules - Added support for 1.4+ firewall rules + - fix tests for 1.4+ firewall rules (ICMP V6 code and type) + - added support for 1.5+ firewall `match-ipsec-in`, `match-ipsec-out`, `match-none-in`, `match-none-out` diff --git a/changelogs/fragments/interfaces_update.yml b/changelogs/fragments/interfaces_update.yml new file mode 100644 index 00000000..e9a6d21b --- /dev/null +++ b/changelogs/fragments/interfaces_update.yml @@ -0,0 +1,9 @@ +--- +minor_changes: + - fixed bug where 'replace' would delete an active disable and not reinstate it + - make l3_interfaces pick up loopback interfaces + - added tests for verifying both of these + - fixed over-zealous handling of disable, which could catch other interface + items that are disabled. + - enable support for 1.4 firewall + - support for 1.4 ospf diff --git a/changelogs/fragments/tests.yml b/changelogs/fragments/tests.yml new file mode 100644 index 00000000..78e3d597 --- /dev/null +++ b/changelogs/fragments/tests.yml @@ -0,0 +1,3 @@ +--- +trivial: + - ignore 2.19 sanity tests for now diff --git a/docs/vyos.vyos.vyos_firewall_global_module.rst b/docs/vyos.vyos.vyos_firewall_global_module.rst index 34293b1b..a77ce80f 100644 --- a/docs/vyos.vyos.vyos_firewall_global_module.rst +++ b/docs/vyos.vyos.vyos_firewall_global_module.rst @@ -1,1794 +1,1796 @@ .. _vyos.vyos.vyos_firewall_global_module: ****************************** vyos.vyos.vyos_firewall_global ****************************** **FIREWALL global resource module** Version added: 1.0.0 .. contents:: :local: :depth: 1 Synopsis -------- - This module manage global policies or configurations for firewall on VyOS devices. Parameters ---------- .. raw:: html
Parameter Choices/Defaults Comments
config
dictionary
A dictionary of Firewall global configuration options.
config_trap
boolean
    Choices:
  • no
  • yes
SNMP trap generation on firewall configuration changes.
group
dictionary
Defines a group of objects for referencing in firewall rules.
address_group
list / elements=dictionary
Defines a group of IP addresses for referencing in firewall rules.
afi
string
    Choices:
  • ipv4 ←
  • ipv6
Specifies IP address type
description
string
Allows you to specify a brief description for the address group.
members
list / elements=dictionary
Address-group members.
IPv4 address to match.
IPv4 range to match.
address
string
IP address.
name
string / required
Name of the firewall address group.
network_group
list / elements=dictionary
Defines a group of networks for referencing in firewall rules.
afi
string
    Choices:
  • ipv4 ←
  • ipv6
Specifies network address type
description
string
Allows you to specify a brief description for the network group.
members
list / elements=dictionary
Adds an IPv4 network to the specified network group.
The format is ip-address/prefix.
address
string
IP address.
name
string / required
Name of the firewall network group.
port_group
list / elements=dictionary
Defines a group of ports for referencing in firewall rules.
description
string
Allows you to specify a brief description for the port group.
members
list / elements=dictionary
Port-group member.
port
string
Defines the number.
name
string / required
Name of the firewall port group.
log_martians
boolean
    Choices:
  • no
  • yes
Specifies whether or not to record packets with invalid addresses in the log.
(True) Logs packets with invalid addresses.
(False) Does not log packets with invalid addresses.
ping
dictionary
Policy for handling of all IPv4 ICMP echo requests.
all
boolean
    Choices:
  • no
  • yes
Enables or disables response to all IPv4 ICMP Echo Request (ping) messages.
The system responds to IPv4 ICMP Echo Request messages.
broadcast
boolean
    Choices:
  • no
  • yes
Enables or disables response to broadcast IPv4 ICMP Echo Request and Timestamp Request messages.
IPv4 ICMP Echo and Timestamp Request messages are not processed.
route_redirects
list / elements=dictionary
-A dictionary of Firewall icmp redirect and source route global configuration options.
afi
string / required
    Choices:
  • ipv4
  • ipv6
Specifies IP address type
icmp_redirects
dictionary
Specifies whether to allow sending/receiving of IPv4/v6 ICMP redirect messages.
receive
boolean
    Choices:
  • no
  • yes
Permits or denies receiving packets ICMP redirect messages.
send
boolean
    Choices:
  • no
  • yes
Permits or denies transmitting packets ICMP redirect messages.
ip_src_route
boolean
    Choices:
  • no
  • yes
Specifies whether or not to process source route IP options.
state_policy
list / elements=dictionary
Specifies global firewall state-policy.
action
string
    Choices:
  • accept
  • drop
  • reject
Action for packets part of an established connection.
connection_type
string
    Choices:
  • established
  • invalid
  • related
Specifies connection type.
log
boolean
    Choices:
  • no
  • yes
Enable logging of packets part of an established connection.
syn_cookies
boolean
    Choices:
  • no
  • yes
Specifies policy for using TCP SYN cookies with IPv4.
(True) Enables TCP SYN cookies with IPv4.
(False) Disables TCP SYN cookies with IPv4.
twa_hazards_protection
boolean
    Choices:
  • no
  • yes
RFC1337 TCP TIME-WAIT assassination hazards protection.
validation
string
    Choices:
  • strict
  • loose
  • disable
Specifies a policy for source validation by reversed path, as defined in RFC 3704.
(disable) No source validation is performed.
(loose) Enable Loose Reverse Path Forwarding as defined in RFC3704.
(strict) Enable Strict Reverse Path Forwarding as defined in RFC3704.
running_config
string
The module, by default, will connect to the remote device and retrieve the current running-config to use as a base for comparing against the contents of source. There are times when it is not desirable to have the task get the current running-config for every task in a playbook. The running_config argument allows the implementer to pass in the configuration to use as the base config for comparison. This value of this option should be the output received from device by executing command show configuration commands | grep 'firewall'
state
string
    Choices:
  • merged ←
  • replaced
  • deleted
  • gathered
  • rendered
  • parsed
The state the configuration should be left in.

Notes ----- .. note:: - Tested against VyOS 1.1.8 (helium). - This module works with connection ``ansible.netcommon.network_cli``. See `the VyOS OS Platform Options <../network/user_guide/platform_vyos.html>`_. Examples -------- .. code-block:: yaml # Using merged # # Before state: # ------------- # # vyos@vyos# run show configuration commands | grep firewall # # - name: Merge the provided configuration with the existing running configuration vyos.vyos.vyos_firewall_global: config: validation: strict config_trap: true log_martians: true syn_cookies: true twa_hazards_protection: true ping: all: true broadcast: true state_policy: - connection_type: established action: accept log: true + log_level: emer - connection_type: invalid action: reject route_redirects: - afi: ipv4 ip_src_route: true icmp_redirects: send: true receive: false group: address_group: - name: MGMT-HOSTS description: This group has the Management hosts address list members: - address: 192.0.1.1 - address: 192.0.1.3 - address: 192.0.1.5 network_group: - name: MGMT description: This group has the Management network addresses members: - address: 192.0.1.0/24 state: merged # # # ------------------------- # Module Execution Result # ------------------------- # # before": [] # # "commands": [ # "set firewall group address-group MGMT-HOSTS address 192.0.1.1", # "set firewall group address-group MGMT-HOSTS address 192.0.1.3", # "set firewall group address-group MGMT-HOSTS address 192.0.1.5", # "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list'", # "set firewall group address-group MGMT-HOSTS", # "set firewall group network-group MGMT network 192.0.1.0/24", # "set firewall group network-group MGMT description 'This group has the Management network addresses'", # "set firewall group network-group MGMT", # "set firewall ip-src-route 'enable'", # "set firewall receive-redirects 'disable'", # "set firewall send-redirects 'enable'", # "set firewall config-trap 'enable'", # "set firewall state-policy established action 'accept'", # "set firewall state-policy established log 'enable'", + # "set firewall state-policy established log-level 'emer'", # "set firewall state-policy invalid action 'reject'", # "set firewall broadcast-ping 'enable'", # "set firewall all-ping 'enable'", # "set firewall log-martians 'enable'", # "set firewall twa-hazards-protection 'enable'", # "set firewall syn-cookies 'enable'", # "set firewall source-validation 'strict'" # ] # # "after": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "This group has the Management hosts address list", # "members": [ # { # "address": "192.0.1.1" # }, # { # "address": "192.0.1.3" # }, # { # "address": "192.0.1.5" # } # ], # "name": "MGMT-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # After state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group MGMT-HOSTS address '192.0.1.1' # set firewall group address-group MGMT-HOSTS address '192.0.1.3' # set firewall group address-group MGMT-HOSTS address '192.0.1.5' # set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # # # Using parsed # # - name: Render the commands for provided configuration vyos.vyos.vyos_firewall_global: running_config: "set firewall all-ping 'enable' set firewall broadcast-ping 'enable' set firewall config-trap 'enable' set firewall group address-group ENG-HOSTS address '192.0.3.1' set firewall group address-group ENG-HOSTS address '192.0.3.2' set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' set firewall group address-group SALES-HOSTS address '192.0.2.1' set firewall group address-group SALES-HOSTS address '192.0.2.2' set firewall group address-group SALES-HOSTS address '192.0.2.3' set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' set firewall group network-group MGMT description 'This group has the Management network addresses' set firewall group network-group MGMT network '192.0.1.0/24' set firewall ip-src-route 'enable' set firewall log-martians 'enable' set firewall receive-redirects 'disable' set firewall send-redirects 'enable' set firewall source-validation 'strict' set firewall state-policy established action 'accept' set firewall state-policy established log 'enable' set firewall state-policy invalid action 'reject' set firewall syn-cookies 'enable' set firewall twa-hazards-protection 'enable'" state: parsed # # # ------------------------- # Module Execution Result # ------------------------- # # # "parsed": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.3.1" # }, # { # "address": "192.0.3.2" # } # ], # "name": "ENG-HOSTS" # }, # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.2.1" # }, # { # "address": "192.0.2.2" # }, # { # "address": "192.0.2.3" # } # ], # "name": "SALES-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # } # # # Using deleted # # Before state # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group MGMT-HOSTS address '192.0.1.1' # set firewall group address-group MGMT-HOSTS address '192.0.1.3' # set firewall group address-group MGMT-HOSTS address '192.0.1.5' # set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' - name: Delete attributes of firewall. vyos.vyos.vyos_firewall_global: config: state_policy: config_trap: log_martians: syn_cookies: twa_hazards_protection: route_redirects: ping: group: state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "This group has the Management hosts address list", # "members": [ # { # "address": "192.0.1.1" # }, # { # "address": "192.0.1.3" # }, # { # "address": "192.0.1.5" # } # ], # "name": "MGMT-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # "commands": [ # "delete firewall source-validation", # "delete firewall group", # "delete firewall log-martians", # "delete firewall ip-src-route", # "delete firewall receive-redirects", # "delete firewall send-redirects", # "delete firewall config-trap", # "delete firewall state-policy", # "delete firewall syn-cookies", # "delete firewall broadcast-ping", # "delete firewall all-ping", # "delete firewall twa-hazards-protection" # ] # # "after": [] # # After state # ------------ # vyos@192# run show configuration commands | grep firewall # set 'firewall' # # # Using replaced # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group MGMT-HOSTS address '192.0.1.1' # set firewall group address-group MGMT-HOSTS address '192.0.1.3' # set firewall group address-group MGMT-HOSTS address '192.0.1.5' # set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' - name: Replace firewall global attributes configuration. vyos.vyos.vyos_firewall_global: config: validation: strict config_trap: true log_martians: true syn_cookies: true twa_hazards_protection: true ping: null all: true broadcast: true state_policy: - connection_type: established action: accept log: true - connection_type: invalid action: reject route_redirects: - afi: ipv4 ip_src_route: true icmp_redirects: send: true receive: false group: address_group: - name: SALES-HOSTS description: Sales office hosts address list members: - address: 192.0.2.1 - address: 192.0.2.2 - address: 192.0.2.3 - name: ENG-HOSTS description: Sales office hosts address list members: - address: 192.0.3.1 - address: 192.0.3.2 network_group: - name: MGMT description: This group has the Management network addresses members: - address: 192.0.1.0/24 state: replaced # # # ------------------------- # Module Execution Result # ------------------------- # # "before": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "This group has the Management hosts address list", # "members": [ # { # "address": "192.0.1.1" # }, # { # "address": "192.0.1.3" # }, # { # "address": "192.0.1.5" # } # ], # "name": "MGMT-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # "commands": [ # "delete firewall group address-group MGMT-HOSTS", # "set firewall group address-group SALES-HOSTS address 192.0.2.1", # "set firewall group address-group SALES-HOSTS address 192.0.2.2", # "set firewall group address-group SALES-HOSTS address 192.0.2.3", # "set firewall group address-group SALES-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group SALES-HOSTS", # "set firewall group address-group ENG-HOSTS address 192.0.3.1", # "set firewall group address-group ENG-HOSTS address 192.0.3.2", # "set firewall group address-group ENG-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group ENG-HOSTS" # ] # # "after": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.3.1" # }, # { # "address": "192.0.3.2" # } # ], # "name": "ENG-HOSTS" # }, # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.2.1" # }, # { # "address": "192.0.2.2" # }, # { # "address": "192.0.2.3" # } # ], # "name": "SALES-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # After state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group ENG-HOSTS address '192.0.3.1' # set firewall group address-group ENG-HOSTS address '192.0.3.2' # set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' # set firewall group address-group SALES-HOSTS address '192.0.2.1' # set firewall group address-group SALES-HOSTS address '192.0.2.2' # set firewall group address-group SALES-HOSTS address '192.0.2.3' # set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # # # Using gathered # # Before state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group ENG-HOSTS address '192.0.3.1' # set firewall group address-group ENG-HOSTS address '192.0.3.2' # set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' # set firewall group address-group SALES-HOSTS address '192.0.2.1' # set firewall group address-group SALES-HOSTS address '192.0.2.2' # set firewall group address-group SALES-HOSTS address '192.0.2.3' # set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # - name: Gather firewall global config with provided configurations vyos.vyos.vyos_firewall_global: state: gathered # # # ------------------------- # Module Execution Result # ------------------------- # # "gathered": [ # { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.3.1" # }, # { # "address": "192.0.3.2" # } # ], # "name": "ENG-HOSTS" # }, # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.2.1" # }, # { # "address": "192.0.2.2" # }, # { # "address": "192.0.2.3" # } # ], # "name": "SALES-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # After state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group ENG-HOSTS address '192.0.3.1' # set firewall group address-group ENG-HOSTS address '192.0.3.2' # set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' # set firewall group address-group SALES-HOSTS address '192.0.2.1' # set firewall group address-group SALES-HOSTS address '192.0.2.2' # set firewall group address-group SALES-HOSTS address '192.0.2.3' # set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # Using rendered # # - name: Render the commands for provided configuration vyos.vyos.vyos_firewall_global: config: validation: strict config_trap: true log_martians: true syn_cookies: true twa_hazards_protection: true ping: null all: true broadcast: true state_policy: - connection_type: established action: accept log: true - connection_type: invalid action: reject route_redirects: - afi: ipv4 ip_src_route: true icmp_redirects: null send: true receive: false group: address_group: - name: SALES-HOSTS description: Sales office hosts address list members: - address: 192.0.2.1 - address: 192.0.2.2 - address: 192.0.2.3 - name: ENG-HOSTS description: Sales office hosts address list members: - address: 192.0.3.1 - address: 192.0.3.2 network_group: - name: MGMT description: This group has the Management network addresses members: - address: 192.0.1.0/24 state: rendered # # # ------------------------- # Module Execution Result # ------------------------- # # # "rendered": [ # "set firewall group address-group SALES-HOSTS address 192.0.2.1", # "set firewall group address-group SALES-HOSTS address 192.0.2.2", # "set firewall group address-group SALES-HOSTS address 192.0.2.3", # "set firewall group address-group SALES-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group SALES-HOSTS", # "set firewall group address-group ENG-HOSTS address 192.0.3.1", # "set firewall group address-group ENG-HOSTS address 192.0.3.2", # "set firewall group address-group ENG-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group ENG-HOSTS", # "set firewall group network-group MGMT network 192.0.1.0/24", # "set firewall group network-group MGMT description 'This group has the Management network addresses'", # "set firewall group network-group MGMT", # "set firewall ip-src-route 'enable'", # "set firewall receive-redirects 'disable'", # "set firewall send-redirects 'enable'", # "set firewall config-trap 'enable'", # "set firewall state-policy established action 'accept'", # "set firewall state-policy established log 'enable'", # "set firewall state-policy invalid action 'reject'", # "set firewall broadcast-ping 'enable'", # "set firewall all-ping 'enable'", # "set firewall log-martians 'enable'", # "set firewall twa-hazards-protection 'enable'", # "set firewall syn-cookies 'enable'", # "set firewall source-validation 'strict'" # ] # # Return Values ------------- Common return values are documented `here `_, the following are the fields unique to this module: .. raw:: html
Key Returned Description
after
list
when changed
The resulting configuration model invocation.

Sample:
The configuration returned will always be in the same format of the parameters above.
before
list
always
The configuration prior to the model invocation.

Sample:
The configuration returned will always be in the same format of the parameters above.
commands
list
always
The set of commands pushed to the remote device.

Sample:
['set firewall group address-group ENG-HOSTS', 'set firewall group address-group ENG-HOSTS address 192.0.3.1']


Status ------ Authors ~~~~~~~ - Rohit Thakur (@rohitthakur2590) diff --git a/docs/vyos.vyos.vyos_firewall_rules_module.rst b/docs/vyos.vyos.vyos_firewall_rules_module.rst index 246824b2..b3d619be 100644 --- a/docs/vyos.vyos.vyos_firewall_rules_module.rst +++ b/docs/vyos.vyos.vyos_firewall_rules_module.rst @@ -1,2463 +1,2469 @@ .. _vyos.vyos.vyos_firewall_rules_module: ***************************** vyos.vyos.vyos_firewall_rules ***************************** **FIREWALL rules resource module** Version added: 1.0.0 .. contents:: :local: :depth: 1 Synopsis -------- - This module manages firewall rule-set attributes on VyOS devices Parameters ---------- .. raw:: html
Parameter Choices/Defaults Comments
config
list / elements=dictionary
A dictionary of Firewall rule-set options.
afi
string / required
    Choices:
  • ipv4
  • ipv6
Specifies the type of rule-set.
rule_sets
list / elements=dictionary
The Firewall rule-set list.
default_action
string
    Choices:
  • drop
  • reject
  • accept
Default action for rule-set.
drop (Drop if no prior rules are hit (default))
reject (Drop and notify source if no prior rules are hit)
accept (Accept if no prior rules are hit)
description
string
Rule set description.
enable_default_log
boolean
    Choices:
  • no
  • yes
Option to log packets hitting default-action.
name
string
Firewall rule set name.
rules
list / elements=dictionary
A dictionary that specifies the rule-set configurations.
action
string
    Choices:
  • drop
  • reject
  • accept
  • inspect
Specifying the action.
description
string
Description of this rule.
destination
dictionary
Specifying the destination parameters.
address
string
Destination ip address subnet or range.
IPv4/6 address, subnet or range to match.
Match everything except the specified address, subnet or range.
Destination ip address subnet or range.
group
dictionary
Destination group.
address_group
string
Group of addresses.
network_group
string
Group of networks.
port_group
string
Group of ports.
port
string
Multiple destination ports can be specified as a comma-separated list.
The whole list can also be "negated" using '!'.
For example:'!22,telnet,http,123,1001-1005'.
disable
boolean
    Choices:
  • no
  • yes
Option to disable firewall rule.

aliases: disabled
fragment
string
    Choices:
  • match-frag
  • match-non-frag
IP fragment match.
icmp
dictionary
ICMP type and code information.
code
integer
ICMP code.
type
integer
ICMP type.
type_name
string
    Choices:
  • any
  • echo-reply
  • destination-unreachable
  • network-unreachable
  • host-unreachable
  • protocol-unreachable
  • port-unreachable
  • fragmentation-needed
  • source-route-failed
  • network-unknown
  • host-unknown
  • network-prohibited
  • host-prohibited
  • TOS-network-unreachable
  • TOS-host-unreachable
  • communication-prohibited
  • host-precedence-violation
  • precedence-cutoff
  • source-quench
  • redirect
  • network-redirect
  • host-redirect
  • TOS-network-redirect
  • TOS-host-redirect
  • echo-request
  • router-advertisement
  • router-solicitation
  • time-exceeded
  • ttl-zero-during-transit
  • ttl-zero-during-reassembly
  • parameter-problem
  • ip-header-bad
  • required-option-missing
  • timestamp-request
  • timestamp-reply
  • address-mask-request
  • address-mask-reply
  • ping
  • pong
  • ttl-exceeded
ICMP type-name.
ipsec
string
    Choices: +
    VyOS 1.4 & older:
  • match-ipsec
  • match-none
  • +
    VyOS 1.5+ :
    +
  • match-ipsec-in
  • +
  • match-ipsec-out
  • +
  • match-none-in
  • +
  • match-none-out
Inbound ip sec packets.
limit
dictionary
Rate limit using a token bucket filter.
burst
integer
Maximum number of packets to allow in excess of rate.
rate
dictionary
format for rate (integer/time unit).
any one of second, minute, hour or day may be used to specify time unit.
eg. 1/second implies rule to be matched at an average of once per second.
number
integer
This is the integer value.
unit
string
This is the time unit.
log
string
    Choices:
  • disable
  • enable
Option to log packets matching rule
number
integer / required
Rule number.
p2p
list / elements=dictionary
P2P application packets.
application
string
    Choices:
  • all
  • applejuice
  • bittorrent
  • directconnect
  • edonkey
  • gnutella
  • kazaa
Name of the application.
protocol
string
Protocol to match (protocol name in /etc/protocols or protocol number or all).
<text> IP protocol name from /etc/protocols (e.g. "tcp" or "udp").
<0-255> IP protocol number.
tcp_udp Both TCP and UDP.
all All IP protocols.
(!)All IP protocols except for the specified name or number.
recent
dictionary
Parameters for matching recently seen sources.
count
integer
Source addresses seen more than N times.
time
integer
Source addresses seen in the last N seconds.
source
dictionary
Source parameters.
address
string
Source ip address subnet or range.
IPv4/6 address, subnet or range to match.
Match everything except the specified address, subnet or range.
Source ip address subnet or range.
group
dictionary
Source group.
address_group
string
Group of addresses.
network_group
string
Group of networks.
port_group
string
Group of ports.
mac_address
string
<MAC address> MAC address to match.
<!MAC address> Match everything except the specified MAC address.
port
string
Multiple source ports can be specified as a comma-separated list.
The whole list can also be "negated" using '!'.
For example:'!22,telnet,http,123,1001-1005'.
state
dictionary
Session state.
established
boolean
    Choices:
  • no
  • yes
Established state.
invalid
boolean
    Choices:
  • no
  • yes
Invalid state.
new
boolean
    Choices:
  • no
  • yes
New state.
related
boolean
    Choices:
  • no
  • yes
Related state.
tcp
dictionary
TCP flags to match.
flags
string
TCP flags to be matched.
time
dictionary
Time to match rule.
monthdays
string
Monthdays to match rule on.
startdate
string
Date to start matching rule.
starttime
string
Time of day to start matching rule.
stopdate
string
Date to stop matching rule.
stoptime
string
Time of day to stop matching rule.
utc
boolean
    Choices:
  • no
  • yes
Interpret times for startdate, stopdate, starttime and stoptime to be UTC.
weekdays
string
Weekdays to match rule on.
running_config
string
This option is used only with state parsed.
The value of this option should be the output received from the VyOS device by executing the command show configuration commands | grep firewall.
The state parsed reads the configuration from running_config option and transforms it into Ansible structured data as per the resource module's argspec and the value is then returned in the parsed key within the result.
state
string
    Choices:
  • merged ←
  • replaced
  • overridden
  • deleted
  • gathered
  • rendered
  • parsed
The state the configuration should be left in

Notes ----- .. note:: - Tested against VyOS 1.1.8 (helium). - This module works with connection ``ansible.netcommon.network_cli``. See `the VyOS OS Platform Options <../network/user_guide/platform_vyos.html>`_. Examples -------- .. code-block:: yaml # Using deleted to delete firewall rules based on rule-set name # # Before state # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' - name: Delete attributes of given firewall rules. vyos.vyos.vyos_firewall_rules: config: - afi: ipv4 rule_sets: - name: Downlink state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # "commands": [ # "delete firewall name Downlink" # ] # # "after": [] # After state # ------------ # vyos@vyos# run show configuration commands | grep firewall # set firewall group address-group 'inbound' # Using deleted to delete firewall rules based on afi # # Before state # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' - name: Delete attributes of given firewall rules. vyos.vyos.vyos_firewall_rules: config: - afi: ipv4 state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # "commands": [ # "delete firewall name" # ] # # "after": [] # After state # ------------ # vyos@vyos:~$ show configuration commands| grep firewall # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # Using deleted to delete all the the firewall rules when provided config is empty # # Before state # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' # - name: Delete attributes of given firewall rules. vyos.vyos.vyos_firewall_rules: state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # "commands": [ # "delete firewall name" # ] # # "after": [] # After state # ------------ # vyos@vyos# run show configuration commands | grep firewall # set firewall group address-group 'inbound' # Using merged # # Before state: # ------------- # # vyos@vyos# run show configuration commands | grep firewall # set firewall group address-group 'inbound' # - name: Merge the provided configuration with the existing running configuration vyos.vyos.vyos_firewall_rules: config: - afi: ipv6 rule_sets: - name: UPLINK description: This is ipv6 specific rule-set default_action: accept rules: - number: 1 action: accept description: Fwipv6-Rule 1 is configured by Ansible ipsec: match-ipsec - number: 2 action: accept description: Fwipv6-Rule 2 is configured by Ansible ipsec: match-ipsec - afi: ipv4 rule_sets: - name: INBOUND description: IPv4 INBOUND rule set default_action: accept rules: - number: 101 action: accept description: Rule 101 is configured by Ansible ipsec: match-ipsec - number: 102 action: reject description: Rule 102 is configured by Ansible ipsec: match-ipsec - number: 103 action: accept description: Rule 103 is configured by Ansible destination: group: address_group: inbound source: address: 192.0.2.0 state: established: true new: false invalid: false related: true state: merged # # # ------------------------- # Module Execution Result # ------------------------- # # before": [] # # "commands": [ # "set firewall ipv6-name UPLINK default-action 'accept'", # "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", # "set firewall ipv6-name UPLINK rule 1 action 'accept'", # "set firewall ipv6-name UPLINK rule 1", # "set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible'", # "set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec'", # "set firewall ipv6-name UPLINK rule 2 action 'accept'", # "set firewall ipv6-name UPLINK rule 2", # "set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible'", # "set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec'", # "set firewall name INBOUND default-action 'accept'", # "set firewall name INBOUND description 'IPv4 INBOUND rule set'", # "set firewall name INBOUND rule 101 action 'accept'", # "set firewall name INBOUND rule 101", # "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", # "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 102 action 'reject'", # "set firewall name INBOUND rule 102", # "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", # "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", # "set firewall name INBOUND rule 103 destination group address-group inbound", # "set firewall name INBOUND rule 103", # "set firewall name INBOUND rule 103 source address 192.0.2.0", # "set firewall name INBOUND rule 103 state established enable", # "set firewall name INBOUND rule 103 state related enable", # "set firewall name INBOUND rule 103 state invalid disable", # "set firewall name INBOUND rule 103 state new disable", # "set firewall name INBOUND rule 103 action 'accept'" # ] # # "after": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 102 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 102 # }, # { # "action": "accept", # "description": "Rule 103 is configured by Ansible", # "destination": { # "group": { # "address_group": "inbound" # } # }, # "number": 103, # "source": { # "address": "192.0.2.0" # }, # "state": { # "established": true, # "invalid": false, # "new": false, # "related": true # } # } # ] # } # ] # } # ] # # After state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # Using replaced # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # - name: >- Replace device configurations of listed firewall rules with provided configurations vyos.vyos.vyos_firewall_rules: config: - afi: ipv6 rule_sets: - name: UPLINK description: This is ipv6 specific rule-set default_action: accept - afi: ipv4 rule_sets: - name: INBOUND description: IPv4 INBOUND rule set default_action: accept rules: - number: 101 action: accept description: Rule 101 is configured by Ansible ipsec: match-ipsec - number: 104 action: reject description: Rule 104 is configured by Ansible ipsec: match-none state: replaced # # # ------------------------- # Module Execution Result # ------------------------- # # "before": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 102 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 102 # }, # { # "action": "accept", # "description": "Rule 103 is configured by Ansible", # "destination": { # "group": { # "address_group": "inbound" # } # }, # "number": 103, # "source": { # "address": "192.0.2.0" # }, # "state": { # "established": true, # "invalid": false, # "new": false, # "related": true # } # } # ] # } # ] # } # ] # # "commands": [ # "delete firewall ipv6-name UPLINK rule 1", # "delete firewall ipv6-name UPLINK rule 2", # "delete firewall name INBOUND rule 102", # "delete firewall name INBOUND rule 103", # "set firewall name INBOUND rule 104 action 'reject'", # "set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible'", # "set firewall name INBOUND rule 104", # "set firewall name INBOUND rule 104 ipsec 'match-none'" # ] # # "after": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK" # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 104 is configured by Ansible", # "ipsec": "match-none", # "number": 104 # } # ] # } # ] # } # ] # # After state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 104 action 'reject' # set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' # set firewall name INBOUND rule 104 ipsec 'match-none' # Using overridden # # Before state # -------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 104 action 'reject' # set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' # set firewall name INBOUND rule 104 ipsec 'match-none' # - name: Overrides all device configuration with provided configuration vyos.vyos.vyos_firewall_rules: config: - afi: ipv4 rule_sets: - name: Downlink description: IPv4 INBOUND rule set default_action: accept rules: - number: 501 action: accept description: Rule 501 is configured by Ansible ipsec: match-ipsec - number: 502 action: reject description: Rule 502 is configured by Ansible ipsec: match-ipsec state: overridden # # # ------------------------- # Module Execution Result # ------------------------- # # "before": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK" # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 104 is configured by Ansible", # "ipsec": "match-none", # "number": 104 # } # ] # } # ] # } # ] # # "commands": [ # "delete firewall ipv6-name UPLINK", # "delete firewall name INBOUND", # "set firewall name Downlink default-action 'accept'", # "set firewall name Downlink description 'IPv4 INBOUND rule set'", # "set firewall name Downlink rule 501 action 'accept'", # "set firewall name Downlink rule 501", # "set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible'", # "set firewall name Downlink rule 501 ipsec 'match-ipsec'", # "set firewall name Downlink rule 502 action 'reject'", # "set firewall name Downlink rule 502", # "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'", # "set firewall name Downlink rule 502 ipsec 'match-ipsec'" # # # "after": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # # # After state # ------------ # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' # Using gathered # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # - name: Gather listed firewall rules with provided configurations vyos.vyos.vyos_firewall_rules: state: gathered # # # ------------------------- # Module Execution Result # ------------------------- # # "gathered": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 102 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 102 # }, # { # "action": "accept", # "description": "Rule 103 is configured by Ansible", # "destination": { # "group": { # "address_group": "inbound" # } # }, # "number": 103, # "source": { # "address": "192.0.2.0" # }, # "state": { # "established": true, # "invalid": false, # "new": false, # "related": true # } # } # ] # } # ] # } # ] # # # After state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # Using rendered # # - name: Render the commands for provided configuration vyos.vyos.vyos_firewall_rules: config: - afi: ipv6 rule_sets: - name: UPLINK description: This is ipv6 specific rule-set default_action: accept - afi: ipv4 rule_sets: - name: INBOUND description: IPv4 INBOUND rule set default_action: accept rules: - number: 101 action: accept description: Rule 101 is configured by Ansible ipsec: match-ipsec - number: 102 action: reject description: Rule 102 is configured by Ansible ipsec: match-ipsec - number: 103 action: accept description: Rule 103 is configured by Ansible destination: group: address_group: inbound source: address: 192.0.2.0 state: established: true new: false invalid: false related: true state: rendered # # # ------------------------- # Module Execution Result # ------------------------- # # # "rendered": [ # "set firewall ipv6-name UPLINK default-action 'accept'", # "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", # "set firewall name INBOUND default-action 'accept'", # "set firewall name INBOUND description 'IPv4 INBOUND rule set'", # "set firewall name INBOUND rule 101 action 'accept'", # "set firewall name INBOUND rule 101", # "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", # "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 102 action 'reject'", # "set firewall name INBOUND rule 102", # "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", # "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", # "set firewall name INBOUND rule 103 destination group address-group inbound", # "set firewall name INBOUND rule 103", # "set firewall name INBOUND rule 103 source address 192.0.2.0", # "set firewall name INBOUND rule 103 state established enable", # "set firewall name INBOUND rule 103 state related enable", # "set firewall name INBOUND rule 103 state invalid disable", # "set firewall name INBOUND rule 103 state new disable", # "set firewall name INBOUND rule 103 action 'accept'" # ] # Using parsed # # - name: Parsed the provided input commands. vyos.vyos.vyos_firewall_rules: running_config: "set firewall group address-group 'inbound' set firewall name Downlink default-action 'accept' set firewall name Downlink description 'IPv4 INBOUND rule set' set firewall name Downlink rule 501 action 'accept' set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' set firewall name Downlink rule 501 ipsec 'match-ipsec' set firewall name Downlink rule 502 action 'reject' set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' set firewall name Downlink rule 502 ipsec 'match-ipsec'" state: parsed # # # ------------------------- # Module Execution Result # ------------------------- # # # "parsed": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] Return Values ------------- Common return values are documented `here `_, the following are the fields unique to this module: .. raw:: html
Key Returned Description
after
list
when changed
The resulting configuration model invocation.

Sample:
The configuration returned will always be in the same format of the parameters above.
before
list
always
The configuration prior to the model invocation.

Sample:
The configuration returned will always be in the same format of the parameters above.
commands
list
always
The set of commands pushed to the remote device.

Sample:
["set firewall name Downlink default-action 'accept'", "set firewall name Downlink description 'IPv4 INBOUND rule set'", "set firewall name Downlink rule 501 action 'accept'", "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'", "set firewall name Downlink rule 502 ipsec 'match-ipsec'"]


Status ------ Authors ~~~~~~~ - Rohit Thakur (@rohitthakur2590) diff --git a/plugins/cliconf/vyos.py b/plugins/cliconf/vyos.py index 7e6b0b17..5beffaa1 100644 --- a/plugins/cliconf/vyos.py +++ b/plugins/cliconf/vyos.py @@ -1,338 +1,343 @@ # (c) 2017 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ author: Ansible Networking Team (@ansible-network) name: vyos short_description: Use vyos cliconf to run command on VyOS platform description: - This vyos plugin provides low level abstraction apis for sending and receiving CLI commands from VyOS network devices. version_added: 1.0.0 options: config_commands: description: - Specifies a list of commands that can make configuration changes to the target device. - When `ansible_network_single_user_mode` is enabled, if a command sent to the device is present in this list, the existing cache is invalidated. version_added: 2.0.0 type: list elements: str default: [] vars: - name: ansible_vyos_config_commands """ import json import re from ansible.errors import AnsibleConnectionFailure from ansible.module_utils._text import to_text from ansible.module_utils.common._collections_compat import Mapping from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( NetworkConfig, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list from ansible_collections.ansible.netcommon.plugins.plugin_utils.cliconf_base import CliconfBase class Cliconf(CliconfBase): __rpc__ = CliconfBase.__rpc__ + [ "commit", "discard_changes", "get_diff", "run_commands", ] def __init__(self, *args, **kwargs): super(Cliconf, self).__init__(*args, **kwargs) self._device_info = {} def get_device_info(self): if not self._device_info: device_info = {} device_info["network_os"] = "vyos" reply = self.get("show version") data = to_text(reply, errors="surrogate_or_strict").strip() match = re.search(r"Version:\s*(.*)", data) if match: device_info["network_os_version"] = match.group(1) + if device_info["network_os_version"]: + match = re.search(r"VyOS\s*(\d+\.\d+)", device_info["network_os_version"]) + if match: + device_info["network_os_major_version"] = match.group(1) + match = re.search(r"(?:HW|Hardware) model:\s*(\S+)", data) if match: device_info["network_os_model"] = match.group(1) reply = self.get("show host name") device_info["network_os_hostname"] = to_text( reply, errors="surrogate_or_strict", ).strip() self._device_info = device_info return self._device_info def get_config(self, flags=None, format=None): if format: option_values = self.get_option_values() if format not in option_values["format"]: raise ValueError( "'format' value %s is invalid. Valid values of format are %s" % (format, ", ".join(option_values["format"])), ) if not flags: flags = [] if format == "text": command = "show configuration" else: command = "show configuration commands" command += " ".join(to_list(flags)) command = command.strip() out = self.send_command(command) return out def edit_config(self, candidate=None, commit=True, replace=None, comment=None): resp = {} operations = self.get_device_operations() self.check_edit_config_capability(operations, candidate, commit, replace, comment) results = [] requests = [] self.send_command("configure") for cmd in to_list(candidate): if not isinstance(cmd, Mapping): cmd = {"command": cmd} results.append(self.send_command(**cmd)) requests.append(cmd["command"]) out = self.get("compare") out = to_text(out, errors="surrogate_or_strict") diff_config = out if not out.startswith("No changes") else None if diff_config: if commit: try: self.commit(comment) except AnsibleConnectionFailure as e: msg = "commit failed: %s" % e.message self.discard_changes() raise AnsibleConnectionFailure(msg) else: self.send_command("exit") else: self.discard_changes() else: self.send_command("exit") if ( to_text(self._connection.get_prompt(), errors="surrogate_or_strict") .strip() .endswith("#") ): self.discard_changes() if diff_config: resp["diff"] = diff_config resp["response"] = results resp["request"] = requests return resp def get( self, command=None, prompt=None, answer=None, sendonly=False, newline=True, output=None, check_all=False, ): if not command: raise ValueError("must provide value of command to execute") if output: raise ValueError("'output' value %s is not supported for get" % output) return self.send_command( command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all, ) def commit(self, comment=None): if comment: command = 'commit comment "{0}"'.format(comment) else: command = "commit" self.send_command(command) def discard_changes(self): self.send_command("exit discard") def get_diff( self, candidate=None, running=None, diff_match="line", diff_ignore_lines=None, path=None, diff_replace=None, ): diff = {} device_operations = self.get_device_operations() option_values = self.get_option_values() if candidate is None and device_operations["supports_generate_diff"]: raise ValueError("candidate configuration is required to generate diff") if diff_match not in option_values["diff_match"]: raise ValueError( "'match' value %s in invalid, valid values are %s" % (diff_match, ", ".join(option_values["diff_match"])), ) if diff_replace: raise ValueError("'replace' in diff is not supported") if diff_ignore_lines: raise ValueError("'diff_ignore_lines' in diff is not supported") if path: raise ValueError("'path' in diff is not supported") set_format = candidate.startswith("set") or candidate.startswith("delete") candidate_obj = NetworkConfig(indent=4, contents=candidate) if not set_format: config = [c.line for c in candidate_obj.items] commands = list() # this filters out less specific lines for item in config: for index, entry in enumerate(commands): if item.startswith(entry): del commands[index] break commands.append(item) candidate_commands = ["set %s" % cmd.replace(" {", "") for cmd in commands] else: candidate_commands = str(candidate).strip().split("\n") if diff_match == "none": diff["config_diff"] = list(candidate_commands) return diff running_commands = [str(c).replace("'", "") for c in running.splitlines()] updates = list() visited = set() for line in candidate_commands: item = str(line).replace("'", "") if not item.startswith("set") and not item.startswith("delete"): raise ValueError("line must start with either `set` or `delete`") elif item.startswith("set") and item not in running_commands: updates.append(line) elif item.startswith("delete"): if not running_commands: updates.append(line) else: item = re.sub(r"delete", "set", item) for entry in running_commands: if entry.startswith(item) and line not in visited: updates.append(line) visited.add(line) diff["config_diff"] = list(updates) return diff def run_commands(self, commands=None, check_rc=True): if commands is None: raise ValueError("'commands' value is required") responses = list() for cmd in to_list(commands): if not isinstance(cmd, Mapping): cmd = {"command": cmd} output = cmd.pop("output", None) if output: raise ValueError("'output' value %s is not supported for run_commands" % output) try: out = self.send_command(**cmd) except AnsibleConnectionFailure as e: if check_rc: raise out = getattr(e, "err", e) responses.append(out) return responses def get_device_operations(self): return { "supports_diff_replace": False, "supports_commit": True, "supports_rollback": False, "supports_defaults": False, "supports_onbox_diff": True, "supports_commit_comment": True, "supports_multiline_delimiter": False, "supports_diff_match": True, "supports_diff_ignore_lines": False, "supports_generate_diff": False, "supports_replace": False, } def get_option_values(self): return { "format": ["text", "set"], "diff_match": ["line", "none"], "diff_replace": [], "output": [], } def get_capabilities(self): result = super(Cliconf, self).get_capabilities() result["device_operations"] = self.get_device_operations() result.update(self.get_option_values()) return json.dumps(result) def set_cli_prompt_context(self): """ Make sure we are in the operational cli mode :return: None """ if self._connection.connected: self._update_cli_prompt_context(config_context="#", exit_command="exit discard") diff --git a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py index 2326bea1..f79454ed 100644 --- a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py +++ b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py @@ -1,162 +1,165 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################# # WARNING # ############################################# # # This file is auto generated by the resource # module builder playbook. # # Do not edit this file manually. # # Changes to this file will be over written # by the resource module builder. # # Changes should be made in the model used to # generate this file or in the resource module # builder template. # ############################################# """ The arg spec for the vyos_firewall_global module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Firewall_globalArgs(object): # pylint: disable=R0903 """The arg spec for the vyos_firewall_global module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "options": { "config_trap": {"type": "bool"}, "group": { "options": { "address_group": { "elements": "dict", "options": { "afi": { "choices": ["ipv4", "ipv6"], "default": "ipv4", "type": "str", }, "description": {"type": "str"}, "members": { "elements": "dict", "options": {"address": {"type": "str"}}, "type": "list", }, "name": {"required": True, "type": "str"}, }, "type": "list", }, "network_group": { "elements": "dict", "options": { "afi": { "choices": ["ipv4", "ipv6"], "default": "ipv4", "type": "str", }, "description": {"type": "str"}, "members": { "elements": "dict", "options": {"address": {"type": "str"}}, "type": "list", }, "name": {"required": True, "type": "str"}, }, "type": "list", }, "port_group": { "elements": "dict", "options": { "description": {"type": "str"}, "members": { "elements": "dict", "options": {"port": {"type": "str"}}, "type": "list", }, "name": {"required": True, "type": "str"}, }, "type": "list", }, }, "type": "dict", }, "log_martians": {"type": "bool"}, "ping": { "options": { "all": {"type": "bool"}, "broadcast": {"type": "bool"}, }, "type": "dict", }, "route_redirects": { "elements": "dict", "options": { "afi": { "choices": ["ipv4", "ipv6"], "required": True, "type": "str", }, "icmp_redirects": { "options": { "receive": {"type": "bool"}, "send": {"type": "bool"}, }, "type": "dict", }, "ip_src_route": {"type": "bool"}, }, "type": "list", }, "state_policy": { "elements": "dict", "options": { "action": { "choices": ["accept", "drop", "reject"], "type": "str", }, "connection_type": { "choices": ["established", "invalid", "related"], "type": "str", }, "log": {"type": "bool"}, + "log_level": { + "choices": ["emerg", "alert", "crit", "err", "warn", "notice", "info", "debug"] + } }, "type": "list", }, "syn_cookies": {"type": "bool"}, "twa_hazards_protection": {"type": "bool"}, "validation": { "choices": ["strict", "loose", "disable"], "type": "str", }, }, "type": "dict", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "deleted", "gathered", "rendered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py index eb285cfd..4d0973e3 100644 --- a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py @@ -1,262 +1,359 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################# # WARNING # ############################################# # # This file is auto generated by the resource # module builder playbook. # # Do not edit this file manually. # # Changes to this file will be over written # by the resource module builder. # # Changes should be made in the model used to # generate this file or in the resource module # builder template. # ############################################# """ The arg spec for the vyos_firewall_rules module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Firewall_rulesArgs(object): # pylint: disable=R0903 """The arg spec for the vyos_firewall_rules module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "elements": "dict", "options": { "afi": { "choices": ["ipv4", "ipv6"], "required": True, "type": "str", }, "rule_sets": { "elements": "dict", "options": { "default_action": { - "choices": ["drop", "reject", "accept"], + "choices": ["drop", "reject", "accept", "jump"], "type": "str", }, + "default_jump_target": {"type": "str"}, "description": {"type": "str"}, "enable_default_log": {"type": "bool"}, + "filter": { + "choices": ["input", "output", "forward"], + "type": "str" + }, "name": {"type": "str"}, "rules": { "elements": "dict", "options": { "action": { "choices": [ "drop", "reject", "accept", "inspect", + "continue", + "return", + "jump", + "queue", + "synproxy", ], "type": "str", }, "description": {"type": "str"}, "destination": { "options": { "address": {"type": "str"}, "group": { "options": { "address_group": {"type": "str"}, "network_group": {"type": "str"}, "port_group": {"type": "str"}, }, "type": "dict", }, "port": {"type": "str"}, }, "type": "dict", }, "disable": { "type": "bool", "aliases": ["disabled"], }, "fragment": { "choices": [ "match-frag", "match-non-frag", ], "type": "str", }, "icmp": { "options": { "code": {"type": "int"}, "type": {"type": "int"}, "type_name": { "choices": [ "any", "echo-reply", "destination-unreachable", "network-unreachable", "host-unreachable", "protocol-unreachable", "port-unreachable", "fragmentation-needed", "source-route-failed", "network-unknown", "host-unknown", "network-prohibited", "host-prohibited", "TOS-network-unreachable", "TOS-host-unreachable", "communication-prohibited", "host-precedence-violation", "precedence-cutoff", "source-quench", "redirect", "network-redirect", "host-redirect", "TOS-network-redirect", "TOS-host-redirect", "echo-request", "router-advertisement", "router-solicitation", "time-exceeded", "ttl-zero-during-transit", "ttl-zero-during-reassembly", "parameter-problem", "ip-header-bad", "required-option-missing", "timestamp-request", "timestamp-reply", "address-mask-request", "address-mask-reply", "ping", "pong", "ttl-exceeded", ], "type": "str", }, }, "type": "dict", }, + "inbound_interface": { + "options": { + "group": { + "type": "str", + }, + "name": { + "type": "str", + }, + }, + "type": "dict", + }, "ipsec": { - "choices": ["match-ipsec", "match-none"], - "type": "str", + "choices": ["match-ipsec", "match-none", "match-ipsec-in", "match-ipsec-out", "match-none-in", "match-none-out"], + "type": "str" + }, + "jump_target": { + "type": "str" }, "limit": { "options": { "burst": {"type": "int"}, "rate": { "options": { "number": {"type": "int"}, "unit": {"type": "str"}, }, "type": "dict", }, }, "type": "dict", }, "log": { "type": "str", "choices": ["enable", "disable"], }, "number": {"required": True, "type": "int"}, + "outbound_interface": { + "options": { + "group": { + "type": "str", + }, + "name": { + "type": "str", + }, + }, + "type": "dict", + }, "p2p": { "elements": "dict", "options": { "application": { "choices": [ "all", "applejuice", "bittorrent", "directconnect", "edonkey", "gnutella", "kazaa", ], "type": "str", }, }, + "type": "list" + }, + "packet_length": { + "elements": "dict", + "options": { + "length": { + "type": "str", + }, + }, + "type": "list" + }, + "packet_length_exclude": { + "elements": "dict", + "options": { + "length": { + "type": "str", + } + }, "type": "list", }, + "packet_type": { + "choices": [ + "broadcast", + "multicast", + "host", + "other" + ], + "type": "str" + }, "protocol": {"type": "str"}, + "queue": {"type": "str"}, + "queue_options": { + "choices": ["bypass", "fanout"], + "type": "str" + }, "recent": { "options": { "count": {"type": "int"}, - "time": {"type": "int"}, + "time": {"type": "str"}, }, "type": "dict", }, "source": { "options": { "address": {"type": "str"}, + "fqdn": {"type": "str"}, "group": { "options": { "address_group": {"type": "str"}, "network_group": {"type": "str"}, "port_group": {"type": "str"}, }, "type": "dict", }, "mac_address": {"type": "str"}, "port": {"type": "str"}, }, "type": "dict", }, "state": { "options": { "established": {"type": "bool"}, "invalid": {"type": "bool"}, "new": {"type": "bool"}, "related": {"type": "bool"}, }, "type": "dict", }, + "synproxy": { + "options": { + "mss": {"type": "int"}, + "window_scale": {"type": "int"}, + }, + "type": "dict", + }, "tcp": { - "options": {"flags": {"type": "str"}}, + "options": { + "flags": { + "elements": "dict", + "options": { + "flag": { + "choices": [ + "ack", + "cwr", + "ecn", + "fin", + "psh", + "rst", + "syn", + "urg", + "all", + ], + "type": "str" + }, + "invert": {"type": "bool"} + }, + "type": "list" + } + }, "type": "dict", }, "time": { "options": { "monthdays": {"type": "str"}, "startdate": {"type": "str"}, "starttime": {"type": "str"}, "stopdate": {"type": "str"}, "stoptime": {"type": "str"}, "utc": {"type": "bool"}, "weekdays": {"type": "str"}, }, "type": "dict", }, }, "type": "list", }, }, "type": "list", }, }, "type": "list", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "overridden", "deleted", "gathered", "rendered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py index 8694f11b..7e978ff9 100644 --- a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py +++ b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py @@ -1,732 +1,758 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos_firewall_global class It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to it's desired end-state is created """ from __future__ import absolute_import, division, print_function __metaclass__ = type from copy import deepcopy from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_empties, to_list, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( list_diff_want_only, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Firewall_global(ConfigBase): """ The vyos_firewall_global class """ gather_subset = ["!all", "!min"] gather_network_resources = ["firewall_global"] def __init__(self, module): super(Firewall_global, self).__init__(module) def get_firewall_global_facts(self, data=None): """Get the 'facts' (the current configuration) :rtype: A dictionary :returns: The current configuration as a dictionary """ facts, _warnings = Facts(self._module).get_facts( self.gather_subset, self.gather_network_resources, data=data, ) firewall_global_facts = facts["ansible_network_resources"].get("firewall_global") if not firewall_global_facts: return [] return firewall_global_facts def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ result = {"changed": False} warnings = list() commands = list() if self.state in self.ACTION_STATES: existing_firewall_global_facts = self.get_firewall_global_facts() else: existing_firewall_global_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_firewall_global_facts)) if commands and self.state in self.ACTION_STATES: if not self._module.check_mode: self._connection.edit_config(commands) result["changed"] = True if self.state in self.ACTION_STATES: result["commands"] = commands if self.state in self.ACTION_STATES or self.state == "gathered": changed_firewall_global_facts = self.get_firewall_global_facts() elif self.state == "rendered": result["rendered"] = commands elif self.state == "parsed": running_config = self._module.params["running_config"] if not running_config: self._module.fail_json( msg="value of running_config parameter must not be empty for state parsed", ) result["parsed"] = self.get_firewall_global_facts(data=running_config) else: changed_firewall_global_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_firewall_global_facts if result["changed"]: result["after"] = changed_firewall_global_facts elif self.state == "gathered": result["gathered"] = changed_firewall_global_facts result["warnings"] = warnings return result def set_config(self, existing_firewall_global_facts): """Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ want = self._module.params["config"] have = existing_firewall_global_facts resp = self.set_state(want, have) return to_list(resp) def set_state(self, w, h): """Select the appropriate function based on the state provided :param want: the desired configuration as a dictionary :param have: the current configuration as a dictionary :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] if self.state in ("merged", "replaced", "rendered") and not w: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format(self.state), ) if self.state == "deleted": commands.extend(self._state_deleted(want=None, have=h)) elif w: if self.state == "merged" or self.state == "rendered": commands.extend(self._state_merged(w, h)) elif self.state == "replaced": commands.extend(self._state_replaced(w, h)) return commands def _state_replaced(self, w, h): """The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] if h: commands.extend(self._state_deleted(h, w)) commands.extend(self._state_merged(w, h)) return commands def _state_merged(self, want, have): """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] commands.extend(self._add_global_attr(want, have)) return commands def _state_deleted(self, want, have): """The command generator when state is deleted :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ commands = [] b_set = ( "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ) if want: for key, val in iteritems(want): if val and key in b_set and not have: commands.append(self._form_attr_cmd(attr=key, opr=False)) elif val and key in b_set and have and key in have and have[key] != val: commands.append(self._form_attr_cmd(attr=key, opr=False)) else: commands.extend(self._render_attr_config(want, have, key)) elif not want and have: commands.append(self._compute_command(opr=False)) elif have: for key, val in iteritems(have): if val and key in b_set: commands.append(self._form_attr_cmd(attr=key, opr=False)) else: commands.extend(self._render_attr_config(want, have, key)) return commands def _render_attr_config(self, w, h, key, opr=False): """ This function invoke the function to extend commands based on the key. :param w: the desired configuration. :param h: the current configuration. :param key: attribute name :param opr: operation :return: list of commands """ commands = [] if key == "ping": commands.extend(self._render_ping(key, w, h, opr=opr)) elif key == "group": commands.extend(self._render_group(key, w, h, opr=opr)) elif key == "state_policy": commands.extend(self._render_state_policy(key, w, h, opr=opr)) elif key == "route_redirects": commands.extend(self._render_route_redirects(key, w, h, opr=opr)) return commands def _add_global_attr(self, w, h, opr=True): """ This function forms the set/delete commands based on the 'opr' type for firewall_global attributes. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] w_fg = deepcopy(remove_empties(w)) l_set = ( "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ) if w_fg: for key, val in iteritems(w_fg): if opr and key in l_set and not (h and self._is_w_same(w_fg, h, key)): commands.append( self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr), ) elif not opr: if key and self._is_del(l_set, h): commands.append( self._form_attr_cmd(attr=key, key=self._bool_to_str(val), opr=opr), ) continue if ( key in l_set - and not (h and self._in_target(h, key)) + and not self._in_target(h, key) and not self._is_del(l_set, h) ): commands.append( self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr), ) else: commands.extend(self._render_attr_config(w_fg, h, key, opr)) return commands def _render_ping(self, attr, w, h, opr): """ This function forms the commands for 'ping' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired configuration. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_ping = {} l_set = ("all", "broadcast") if h: h_ping = h.get(attr) or {} if self._is_root_del(w[attr], h_ping, attr): for item, value in iteritems(h[attr]): if not opr and item in l_set: commands.append(self._form_attr_cmd(attr=item, opr=opr)) elif w[attr]: if h and attr in h.keys(): h_ping = h.get(attr) or {} for item, value in iteritems(w[attr]): if ( opr and item in l_set and not (h_ping and self._is_w_same(w[attr], h_ping, item)) ): commands.append( self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr), ) elif ( not opr and item in l_set and not (h_ping and self._is_w_same(w[attr], h_ping, item)) ): commands.append(self._form_attr_cmd(attr=item, opr=opr)) return commands def _render_group(self, attr, w, h, opr): """ This function forms the commands for 'group' attribute based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_grp = {} if not opr and self._is_root_del(h, w, attr): commands.append(self._form_attr_cmd(attr=attr, opr=opr)) else: if h: h_grp = h.get("group") or {} if w: commands.extend(self._render_grp_mem("port_group", w["group"], h_grp, opr)) commands.extend(self._render_grp_mem("address_group", w["group"], h_grp, opr)) commands.extend(self._render_grp_mem("network_group", w["group"], h_grp, opr)) return commands def _render_grp_mem(self, attr, w, h, opr): """ This function forms the commands for group list/members attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_grp = [] w_grp = [] l_set = ("name", "description") if w: w_grp = w.get(attr) or [] if h: h_grp = h.get(attr) or [] if w_grp: for want in w_grp: h = self.search_attrib_in_have(h_grp, want, "name") if "afi" in want and want["afi"] == "ipv6": cmd = self._compute_command(key="group", attr="ipv6-" + attr, opr=opr) else: cmd = self._compute_command(key="group", attr=attr, opr=opr) for key, val in iteritems(want): if val: if opr and key in l_set and not (h and self._is_w_same(want, h, key)): if key == "name": commands.append(cmd + " " + str(val)) else: commands.append( cmd + " " + want["name"] + " " + key + " '" + str(want[key]) + "'", ) elif not opr and key in l_set: if key == "name" and self._is_grp_del(h, want, key): commands.append(cmd + " " + want["name"]) continue if not (h and self._in_target(h, key)) and not self._is_grp_del( h, want, key, ): commands.append(cmd + " " + want["name"] + " " + key) elif key == "members": commands.extend( self._render_ports_addrs( key, want, h, opr, cmd, want["name"], attr, ), ) return commands def _render_ports_addrs(self, attr, w, h, opr, cmd, name, type): """ This function forms the commands for port/address/network group members based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param name: name of group. :param type: group type. :return: generated list of commands. """ commands = [] have = [] if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: if opr: members = list_diff_want_only(want, have) for member in members: commands.append( cmd + " " + name + " " + self._grp_type(type) + " " + member[self._get_mem_type(type)], ) elif not opr and have: members = list_diff_want_only(want, have) for member in members: commands.append( cmd + " " + name + " " + self._grp_type(type) + " " + member[self._get_mem_type(type)], ) return commands def _get_mem_type(self, group): """ This function returns the member type based on the type of group. """ return "port" if group == "port_group" else "address" def _render_state_policy(self, attr, w, h, opr): """ This function forms the commands for 'state-policy' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] have = [] - l_set = ("log", "action", "connection_type") + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + l_set = ("log", "action", "connection_type", "log_level") + else: + l_set = ("log", "action", "connection_type") if not opr and self._is_root_del(h, w, attr): commands.append(self._form_attr_cmd(attr=attr, opr=opr)) else: w_sp = deepcopy(remove_empties(w)) want = w_sp.get(attr) or [] if h: have = h.get(attr) or [] if want: for w in want: h = self.search_attrib_in_have(have, w, "connection_type") for key, val in iteritems(w): if val and key != "connection_type": if opr and key in l_set and not (h and self._is_w_same(w, h, key)): commands.append( self._form_attr_cmd( key=attr + " " + w["connection_type"], attr=key, val=self._bool_to_str(val), opr=opr, ), ) elif not opr and key in l_set: - if not (h and self._in_target(h, key)) and not self._is_del( - l_set, - h, - ): - if key == "action": - commands.append( - self._form_attr_cmd( - attr=attr + " " + w["connection_type"], - opr=opr, - ), - ) - else: - commands.append( - self._form_attr_cmd( - attr=attr + " " + w["connection_type"], - val=self._bool_to_str(val), - opr=opr, - ), - ) + if not h: + commands.append( + self._form_attr_cmd( + attr=attr + " " + w["connection_type"], + opr=opr, + ), + ) + break # delete the whole thing and move on + if (not self._in_target(h, key) or h[key] is None) and (self._in_target(w, key) and w[key]): + # delete if not being replaced and value currently exists + commands.append( + self._form_attr_cmd( + attr=attr + " " + w["connection_type"] + " " + key, + val=self._bool_to_str(val), + opr=opr, + ), + ) return commands def _render_route_redirects(self, attr, w, h, opr): """ This function forms the commands for 'route_redirects' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] have = [] l_set = ("afi", "ip_src_route") if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: for w in want: h = self.search_attrib_in_have(have, w, "afi") + if 'afi' in w: + afi = w['afi'] + else: + if h and 'afi' in h: + afi = h['afi'] + else: + afi = None + afi = None for key, val in iteritems(w): if val and key != "afi": if opr and key in l_set and not (h and self._is_w_same(w, h, key)): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, + type=afi ), ) elif not opr and key in l_set: if self._is_del(l_set, h): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, + type=afi ), ) continue if not (h and self._in_target(h, key)) and not self._is_del(l_set, h): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, + type=afi ), ) elif key == "icmp_redirects": commands.extend(self._render_icmp_redirects(key, w, h, opr)) return commands def _render_icmp_redirects(self, attr, w, h, opr): """ This function forms the commands for 'icmp_redirects' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_red = {} l_set = ("send", "receive") + if w and 'afi' in w: + afi = w['afi'] + else: + if h and 'afi' in h: + afi = h['afi'] + else: + afi = None if w[attr]: if h and attr in h.keys(): h_red = h.get(attr) or {} for item, value in iteritems(w[attr]): if opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)): commands.append( - self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr), + self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr, type=afi) ) elif ( not opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)) ): - commands.append(self._form_attr_cmd(attr=item, opr=opr)) + commands.append(self._form_attr_cmd(attr=item, opr=opr, type=afi)) return commands def search_attrib_in_have(self, have, want, attr): """ This function returns the attribute if it is present in target config. :param have: the target config. :param want: the desired config. :param attr: attribute name . :return: attribute/None """ if have: for h in have: if h[attr] == want[attr]: return h return None - def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): + def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True, type=None): """ This function forms the command for leaf attribute. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. + :param type: AF type of attribute. :return: generated command. """ - command = self._compute_command(key=key, attr=self._map_attrib(attr), val=val, opr=opr) + command = self._compute_command(key=key, attr=self._map_attrib(attr, type=type), val=val, opr=opr) return command def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True): """ This function construct the add/delete command based on passed attributes. :param key: parent key. :param attr: attribute name :param value: value :param remove: True/False. :param opr: True/False. :return: generated command. """ if remove or not opr: cmd = "delete firewall " else: cmd = "set firewall " + if key != "group" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + cmd += "global-options " if key: cmd += key.replace("_", "-") + " " if attr: cmd += attr.replace("_", "-") if val and opr: cmd += " '" + str(val) + "'" - return cmd + return cmd.strip() def _bool_to_str(self, val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return "enable" if str(val) == "True" else "disable" if str(val) == "False" else val def _grp_type(self, val): """ This function returns the group member type based on value argument. :param val: value. :return: member type. """ return ( "address" if val == "address_group" else "network" if val == "network_group" else "port" ) def _is_w_same(self, w, h, key): """ This function checks whether the key value is same in desired and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(self, h, key): """ This function checks whether the target exist and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False def _is_grp_del(self, w, h, key): """ This function checks whether group needed to be deleted based on desired and target configs. :param w: the desired config. :param h: the target config. :param key: group name. :return: True/False. """ return True if h and key in h and (not w or key not in w or not w[key]) else False def _is_root_del(self, w, h, key): """ This function checks whether a root attribute which can have further child attributes needed to be deleted. :param w: the desired config. :param h: the target config. :param key: attribute name. :return: True/False. """ return True if h and key in h and (not w or key not in w or not w[key]) else False def _is_del(self, b_set, h, key="number"): """ This function checks whether attribute needs to be deleted when operation is false and attribute present in present target config. :param b_set: attribute set. :param h: target config. :param key: number. :return: True/False. """ - return key in b_set and not (h and self._in_target(h, key)) + return key in b_set and not self._in_target(h, key) def _map_attrib(self, attrib, type=None): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ regex = attrib.replace("_", "-") if attrib == "send": if type == "ipv6": regex = "ipv6-send-redirects" else: regex = "send-redirects" elif attrib == "ip_src_route": if type == "ipv6": regex = "ipv6-src-route" elif attrib == "receive": if type == "ipv6": regex = "ipv6-receive-redirects" else: regex = "receive-redirects" elif attrib == "disabled": regex = "disable" elif attrib == "all": regex = "all-ping" elif attrib == "broadcast": regex = "broadcast-ping" elif attrib == "validation": regex = "source-validation" return regex diff --git a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py index 09e19d70..106b2b8b 100644 --- a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py @@ -1,870 +1,1077 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos_firewall_rules class It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to it's desired end-state is created """ from __future__ import absolute_import, division, print_function __metaclass__ = type -import re - from copy import deepcopy from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_empties, to_list, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( list_diff_want_only, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Firewall_rules(ConfigBase): """ The vyos_firewall_rules class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "firewall_rules", ] def __init__(self, module): super(Firewall_rules, self).__init__(module) def get_firewall_rules_facts(self, data=None): """Get the 'facts' (the current configuration) :rtype: A dictionary :returns: The current configuration as a dictionary """ facts, _warnings = Facts(self._module).get_facts( self.gather_subset, self.gather_network_resources, data=data, ) firewall_rules_facts = facts["ansible_network_resources"].get("firewall_rules") if not firewall_rules_facts: return [] return firewall_rules_facts def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ result = {"changed": False} warnings = list() commands = list() if self.state in self.ACTION_STATES: existing_firewall_rules_facts = self.get_firewall_rules_facts() else: existing_firewall_rules_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_firewall_rules_facts)) if commands and self.state in self.ACTION_STATES: if not self._module.check_mode: self._connection.edit_config(commands) result["changed"] = True if self.state in self.ACTION_STATES: result["commands"] = commands if self.state in self.ACTION_STATES or self.state == "gathered": changed_firewall_rules_facts = self.get_firewall_rules_facts() elif self.state == "rendered": result["rendered"] = commands elif self.state == "parsed": running_config = self._module.params["running_config"] if not running_config: self._module.fail_json( msg="value of running_config parameter must not be empty for state parsed", ) result["parsed"] = self.get_firewall_rules_facts(data=running_config) else: changed_firewall_rules_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_firewall_rules_facts if result["changed"]: result["after"] = changed_firewall_rules_facts elif self.state == "gathered": result["gathered"] = changed_firewall_rules_facts result["warnings"] = warnings return result def set_config(self, existing_firewall_rules_facts): """Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ want = self._module.params["config"] have = existing_firewall_rules_facts resp = self.set_state(want, have) return to_list(resp) def set_state(self, w, h): """Select the appropriate function based on the state provided :param want: the desired configuration as a dictionary :param have: the current configuration as a dictionary :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] if self.state in ("merged", "replaced", "overridden", "rendered") and not w: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format(self.state), ) if self.state == "overridden": commands.extend(self._state_overridden(w, h)) elif self.state == "deleted": commands.extend(self._state_deleted(w, h)) elif w: if self.state == "merged" or self.state == "rendered": commands.extend(self._state_merged(w, h)) elif self.state == "replaced": commands.extend(self._state_replaced(w, h)) return commands def _state_replaced(self, want, have): """The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] if have: # Iterate over the afi rule sets we already have. for h in have: r_sets = self._get_r_sets(h) # Iterate over each rule set we already have. for rs in r_sets: # In the desired configuration, search for the rule set we # already have (to be replaced by our desired # configuration's rule set). - wanted_rule_set = self.search_r_sets_in_have( - want, - rs["name"], - "r_list", - h["afi"], - ) + rs_id = self._rs_id(rs, h["afi"]) + wanted_rule_set = self.search_r_sets_in_have(want, rs_id, "r_list") if wanted_rule_set is not None: # Remove the rules that we already have if the wanted # rules exist under the same name. commands.extend( self._add_r_sets( h["afi"], want=rs, have=wanted_rule_set, opr=False, ), ) # Merge the desired configuration into what we already have. commands.extend(self._state_merged(want, have)) return commands def _state_overridden(self, want, have): """The command generator when state is overridden :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] if have: for h in have: - r_sets = self._get_r_sets(h) - for rs in r_sets: - w = self.search_r_sets_in_have(want, rs["name"], "r_list", h["afi"]) + have_r_sets = self._get_r_sets(h) + for rs in have_r_sets: + rs_id = self._rs_id(rs, h["afi"]) + w = self.search_r_sets_in_have(want, rs_id, "r_list") if not w: - commands.append(self._compute_command(h["afi"], rs["name"], remove=True)) + commands.append(self._compute_command(rs_id, remove=True)) else: commands.extend(self._add_r_sets(h["afi"], rs, w, opr=False)) commands.extend(self._state_merged(want, have)) return commands def _state_merged(self, want, have): """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] for w in want: r_sets = self._get_r_sets(w) for rs in r_sets: - h = self.search_r_sets_in_have(have, rs["name"], "r_list", w["afi"]) + rs_id = self._rs_id(rs, w["afi"]) + h = self.search_r_sets_in_have(have, rs_id, "r_list") commands.extend(self._add_r_sets(w["afi"], rs, h)) return commands def _state_deleted(self, want, have): """The command generator when state is deleted :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ commands = [] if want: for w in want: r_sets = self._get_r_sets(w) if r_sets: for rs in r_sets: - h = self.search_r_sets_in_have(have, rs["name"], "r_list", w["afi"]) + rs_id = self._rs_id(rs, w["afi"]) + h = self.search_r_sets_in_have(have, rs_id, "r_list") if h: - commands.append(self._compute_command(w["afi"], h["name"], remove=True)) + commands.append(self._compute_command(rs_id, remove=True)) elif have: for h in have: if h["afi"] == w["afi"]: - commands.append(self._compute_command(w["afi"], remove=True)) + commands.append( + self._compute_command(self._rs_id(None, w["afi"]), remove=True) + ) elif have: for h in have: r_sets = self._get_r_sets(h) if r_sets: - commands.append(self._compute_command(afi=h["afi"], remove=True)) + commands.append(self._compute_command(self._rs_id(None, h["afi"]), remove=True)) return commands def _add_r_sets(self, afi, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for rule-sets attributes. :param afi: address type. :param want: desired config. :param have: target config. :param opr: True/False. :return: generated commands list. """ commands = [] - l_set = ("description", "default_action", "enable_default_log") + l_set = ("description", "default_action", "default_jump_target", "enable_default_log") h_rs = {} h_rules = {} w_rs = deepcopy(remove_empties(want)) w_rules = w_rs.pop("rules", None) + rs_id = self._rs_id(want, afi=afi) if have: h_rs = deepcopy(remove_empties(have)) h_rules = h_rs.pop("rules", None) if w_rs: for key, val in iteritems(w_rs): if opr and key in l_set and not (h_rs and self._is_w_same(w_rs, h_rs, key)): if key == "enable_default_log": if val and (not h_rs or key not in h_rs or not h_rs[key]): - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs)) else: - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs)) elif not opr and key in l_set: if ( key == "enable_default_log" and val and h_rs and (key not in h_rs or not h_rs[key]) ): - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs, opr)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr)) elif not (h_rs and self._in_target(h_rs, key)): - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs, opr)) - commands.extend(self._add_rules(afi, want["name"], w_rules, h_rules, opr)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr)) + commands.extend(self._add_rules(rs_id, w_rules, h_rules, opr)) if h_rules: have["rules"] = h_rules if w_rules: want["rules"] = w_rules return commands - def _add_rules(self, afi, name, w_rules, h_rules, opr=True): + def _add_rules(self, rs_id, w_rules, h_rules, opr=True): """ This function forms the set/delete commands based on the 'opr' type for rules attributes. - :param want: desired config. - :param have: target config. + :param rs_id: rule-set identifier. + :param w_rules: desired config. + :param h_rules: target config. + :param opr: True/False. :return: generated commands list. """ commands = [] l_set = ( "ipsec", "action", "number", "protocol", "fragment", "disable", "description", "log", + "jump_target", ) if w_rules: for w in w_rules: - cmd = self._compute_command(afi, name, w["number"], opr=opr) - h = self.search_r_sets_in_have(h_rules, w["number"], type="rules") + cmd = self._compute_command(rs_id, w["number"], opr=opr) + h = self.search_rules_in_have_rs(h_rules, w["number"]) for key, val in iteritems(w): if val: if opr and key in l_set and not (h and self._is_w_same(w, h, key)): if key == "disable": if not (not val and (not h or key not in h or not h[key])): - commands.append(self._add_r_base_attrib(afi, name, key, w)) + commands.append(self._add_r_base_attrib(rs_id, key, w)) else: - commands.append(self._add_r_base_attrib(afi, name, key, w)) + commands.append(self._add_r_base_attrib(rs_id, key, w)) elif not opr: + # Note: if you are experiencing sticky configuration on replace + # you may need to add an explicit check for the key here. Anything that + # doesn't have a custom operation is taken care of by the `l_set` check + # below, but I'm not sure how any of the others work. + # It's possible that historically the delete was forced (but now it's + # checked). if key == "number" and self._is_del(l_set, h): - commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr)) + commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr)) continue - if key == "disable" and val and h and (key not in h or not h[key]): - commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr)) + if ( + key == "tcp" + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_tcp(key, w, h, cmd, opr)) + if ( + key == "state" + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_state(key, w, h, cmd, opr)) + if ( + key == "icmp" + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_icmp(key, w, h, cmd, opr)) + if ( + key in ("packet_length", "packet_length_exclude") + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_packet_length(key, w, h, cmd, opr)) + elif key == "disable" and val and h and (key not in h or not h[key]): + commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr)) + elif key in ("inbound_interface", "outbound_interface") and not ( + h and self._is_w_same(w, h, key) + ): + commands.extend(self._add_interface(key, w, h, cmd, opr)) elif ( key in l_set and not (h and self._in_target(h, key)) and not self._is_del(l_set, h) ): - commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr)) + commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr)) elif key == "p2p": commands.extend(self._add_p2p(key, w, h, cmd, opr)) elif key == "tcp": commands.extend(self._add_tcp(key, w, h, cmd, opr)) elif key == "time": commands.extend(self._add_time(key, w, h, cmd, opr)) elif key == "icmp": commands.extend(self._add_icmp(key, w, h, cmd, opr)) elif key == "state": commands.extend(self._add_state(key, w, h, cmd, opr)) elif key == "limit": commands.extend(self._add_limit(key, w, h, cmd, opr)) elif key == "recent": commands.extend(self._add_recent(key, w, h, cmd, opr)) elif key == "destination" or key == "source": commands.extend(self._add_src_or_dest(key, w, h, cmd, opr)) + elif key in ("packet_length", "packet_length_exclude"): + commands.extend(self._add_packet_length(key, w, h, cmd, opr)) + elif key in ("inbound_interface", "outbound_interface"): + commands.extend(self._add_interface(key, w, h, cmd, opr)) return commands def _add_p2p(self, attr, w, h, cmd, opr): """ This function forms the set/delete commands based on the 'opr' type for p2p applications attributes. :param want: desired config. :param have: target config. :return: generated commands list. """ commands = [] have = [] if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: if opr: applications = list_diff_want_only(want, have) for app in applications: commands.append(cmd + (" " + attr + " " + app["application"])) elif not opr and have: applications = list_diff_want_only(want, have) for app in applications: commands.append(cmd + (" " + attr + " " + app["application"])) return commands def _add_state(self, attr, w, h, cmd, opr): """ This function forms the command for 'state' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ h_state = {} commands = [] l_set = ("new", "invalid", "related", "established") if w[attr]: if h and attr in h.keys(): h_state = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not (h_state and self._is_w_same(w[attr], h_state, item)) ): - commands.append(cmd + (" " + attr + " " + item + " " + self._bool_to_str(val))) - elif not opr and item in l_set and not (h_state and self._in_target(h_state, item)): + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + commands.append(cmd + (" " + attr + " " + item)) + else: + commands.append(cmd + (" " + attr + " " + item + " " + self._bool_to_str(val))) + elif not opr and item in l_set and not self._in_target(h_state, item): commands.append(cmd + (" " + attr + " " + item)) return commands def _add_recent(self, attr, w, h, cmd, opr): """ This function forms the command for 'recent' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_recent = {} l_set = ("count", "time") if w[attr]: if h and attr in h.keys(): h_recent = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not (h_recent and self._is_w_same(w[attr], h_recent, item)) ): commands.append(cmd + (" " + attr + " " + item + " " + str(val))) elif ( not opr and item in l_set and not (h_recent and self._in_target(h_recent, item)) ): commands.append(cmd + (" " + attr + " " + item)) return commands def _add_icmp(self, attr, w, h, cmd, opr): """ This function forms the commands for 'icmp' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_icmp = {} l_set = ("code", "type", "type_name") if w[attr]: if h and attr in h.keys(): h_icmp = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not (h_icmp and self._is_w_same(w[attr], h_icmp, item)) ): if item == "type_name": - os_version = self._get_os_version() - ver = re.search( - "vyos ([\\d\\.]+)-?.*", # noqa: W605 - os_version, - re.IGNORECASE, - ) - if ver.group(1) >= "1.4": + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.3"): param_name = "type-name" else: param_name = "type" - if "ipv6-name" in cmd: + if "ipv6" in cmd: # ipv6-name or ipv6 commands.append(cmd + (" " + "icmpv6" + " " + param_name + " " + val)) else: commands.append( cmd + (" " + attr + " " + item.replace("_", "-") + " " + val), ) else: - commands.append(cmd + (" " + attr + " " + item + " " + str(val))) - elif not opr and item in l_set and not (h_icmp and self._in_target(h_icmp, item)): - commands.append(cmd + (" " + attr + " " + item)) + if "ipv6" in cmd: # ipv6-name or ipv6 + commands.append(cmd + (" " + "icmpv6" + " " + item + " " + str(val))) + else: + commands.append(cmd + (" " + attr + " " + item + " " + str(val))) + elif not opr and item in l_set and not self._in_target(h_icmp, item): + commands.append(cmd + (" " + attr + " " + item.replace("_", "-") + " " + str(val))) + return commands + + def _add_interface(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'interface' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + h_if = {} + l_set = ("name", "group") + if w[attr]: + if h and attr in h.keys(): + h_if = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if opr and item in l_set and not (h_if and self._is_w_same(w[attr], h_if, item)): + commands.append( + cmd + + (" " + attr.replace("_", "-") + " " + item.replace("_", "-") + " " + val) + ) + elif not opr and item in l_set and not (h_if and self._in_target(h_if, item)): + commands.append( + cmd + (" " + attr.replace("_", "-") + " " + item.replace("_", "-")) + ) return commands def _add_time(self, attr, w, h, cmd, opr): """ This function forms the commands for 'time' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_time = {} l_set = ( "utc", "stopdate", "stoptime", "weekdays", "monthdays", "startdate", "starttime", ) if w[attr]: if h and attr in h.keys(): h_time = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not (h_time and self._is_w_same(w[attr], h_time, item)) ): if item == "utc": if not (not val and (not h_time or item not in h_time)): commands.append(cmd + (" " + attr + " " + item)) else: commands.append(cmd + (" " + attr + " " + item + " " + val)) elif ( not opr and item in l_set and not (h_time and self._is_w_same(w[attr], h_time, item)) ): commands.append(cmd + (" " + attr + " " + item)) return commands + def _add_tcp_1_4(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'tcp' attributes based on the 'opr'. + Version 1.4+ + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + have = [] + key = "flags" + want = [] + + if w: + if w.get(attr): + want = w.get(attr).get(key) or [] + if h: + if h.get(attr): + have = h.get(attr).get(key) or [] + if want: + if opr: + flags = list_diff_want_only(want, have) + for flag in flags: + invert = flag.get("invert", False) + commands.append( + cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]) + ) + elif not opr: + flags = list_diff_want_only(want, have) + for flag in flags: + invert = flag.get("invert", False) + commands.append( + cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]) + ) + return commands + + def _add_packet_length(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'packet_length[_exclude]' attributes based on the 'opr'. + If < 1.4, handle tcp attributes. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + have = [] + want = [] + + if w: + if w.get(attr): + want = w.get(attr) or [] + if h: + if h.get(attr): + have = h.get(attr) or [] + attr = attr.replace("_", "-") + if want: + if opr: + lengths = list_diff_want_only(want, have) + for l_rec in lengths: + commands.append(cmd + " " + attr + " " + str(l_rec["length"])) + elif not opr: + lengths = list_diff_want_only(want, have) + for l_rec in lengths: + commands.append(cmd + " " + attr + " " + str(l_rec["length"])) + return commands + + def _tcp_flags_string(self, flags): + """ + This function forms the tcp flags string. + :param flags: flags list. + :return: flags string or None. + """ + if not flags: + return "" + flag_str = "" + for flag in flags: + this_flag = flag["flag"].upper() + if flag.get("invert", False): + this_flag = "!" + this_flag + if len(flag_str) > 0: + flag_str = ",".join([flag_str, this_flag]) + else: + flag_str = this_flag + return flag_str + def _add_tcp(self, attr, w, h, cmd, opr): """ This function forms the commands for 'tcp' attributes based on the 'opr'. + If < 1.4, handle tcp attributes. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + return self._add_tcp_1_4(attr, w, h, cmd, opr) h_tcp = {} commands = [] if w[attr]: key = "flags" flags = w[attr].get(key) or {} if flags: if h and key in h[attr].keys(): h_tcp = h[attr].get(key) or {} if flags: - if opr and not (h_tcp and self._is_w_same(w[attr], h[attr], key)): - commands.append(cmd + (" " + attr + " " + key + " " + flags)) - if not opr and not (h_tcp and self._is_w_same(w[attr], h[attr], key)): - commands.append(cmd + (" " + attr + " " + key + " " + flags)) + flag_str = self._tcp_flags_string(flags) + if opr and not (h_tcp and flags == h_tcp): + commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str)) + if not opr and not (h_tcp and flags == h_tcp): + commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str)) return commands def _add_limit(self, attr, w, h, cmd, opr): """ This function forms the commands for 'limit' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ h_limit = {} commands = [] if w[attr]: key = "burst" if ( opr and key in w[attr].keys() and not (h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key)) ): commands.append(cmd + (" " + attr + " " + key + " " + str(w[attr].get(key)))) elif ( not opr and key in w[attr].keys() and not (h and attr in h.keys() and self._in_target(h[attr], key)) ): commands.append(cmd + (" " + attr + " " + key + " " + str(w[attr].get(key)))) key = "rate" rate = w[attr].get(key) or {} if rate: if h and key in h[attr].keys(): h_limit = h[attr].get(key) or {} if "unit" in rate and "number" in rate: if opr and not ( h_limit and self._is_w_same(rate, h_limit, "unit") and self.is_w_same(rate, h_limit, "number") ): commands.append( cmd + ( " " + attr + " " + key + " " + str(rate["number"]) + "/" + rate["unit"] ), ) if not opr and not ( h_limit and self._is_w_same(rate, h_limit, "unit") and self._is_w_same(rate, h_limit, "number") ): commands.append(cmd + (" " + attr + " " + key)) return commands def _add_src_or_dest(self, attr, w, h, cmd, opr=True): """ This function forms the commands for 'src/dest' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_group = {} g_set = ("port_group", "address_group", "network_group") if w[attr]: keys = ("address", "mac_address", "port") for key in keys: if ( opr and key in w[attr].keys() and not (h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key)) ): commands.append( cmd + (" " + attr + " " + key.replace("_", "-") + " " + w[attr].get(key)), ) elif ( not opr and key in w[attr].keys() and not (h and attr in h.keys() and self._in_target(h[attr], key)) ): commands.append(cmd + (" " + attr + " " + key)) key = "group" group = w[attr].get(key) or {} if group: h_group = {} if h and h.get(attr) and key in h[attr].keys(): h_group = h[attr].get(key) for item, val in iteritems(group): if val: if ( opr and item in g_set and not (h_group and self._is_w_same(group, h_group, item)) ): commands.append( cmd + ( " " + attr + " " + key + " " + item.replace("_", "-") + " " + val ), ) elif ( not opr and item in g_set and not (h_group and self._in_target(h_group, item)) ): commands.append( cmd + (" " + attr + " " + key + " " + item.replace("_", "-")), ) return commands - def search_r_sets_in_have(self, have, w_name, type="rule_sets", afi=None): + def search_rules_in_have_rs(self, have_rules, r_number): + """ + This function returns the rule if it is present in target config. + :param have: target config. + :param rs_id: rule-set identifier. + :param r_number: rule-number. + :return: rule. + """ + if have_rules: + for h in have_rules: + key = "number" + for r in have_rules: + if key in r and r[key] == r_number: + return r + return None + + def search_r_sets_in_have(self, have, rs_id, type="rule_sets"): """ This function returns the rule-set/rule if it is present in target config. :param have: target config. - :param w_name: rule-set name. - :param type: rule_sets/rule/r_list. - :param afi: address family (when type is r_list). + :param rs_id: rule-identifier. + :param type: rule_sets if searching a rule_set and r_list if searching from a rule_list. :return: rule-set/rule. """ - if have: + if "afi" in rs_id: + afi = rs_id["afi"] + else: + afi = None + if rs_id["filter"]: + key = "filter" + w_value = rs_id["filter"] + elif rs_id["name"]: key = "name" - if type == "rules": - key = "number" - for r in have: - if r[key] == w_name: - return r - elif type == "r_list": + w_value = rs_id["name"] + else: + raise ValueError("id must be specific to name or filter") + + if type not in ("r_list", "rule_sets"): + raise ValueError("type must be rule_sets or r_list") + if have: + if type == "r_list": for h in have: if h["afi"] == afi: r_sets = self._get_r_sets(h) for rs in r_sets: - if rs[key] == w_name: + if key in rs and rs[key] == w_value: return rs else: + # searching a ruleset for rs in have: - if rs[key] == w_name: + if key in rs and rs[key] == w_value: return rs return None - def _get_r_sets(self, item, type="rule_sets"): + def _get_r_sets(self, item): """ - This function returns the list of rule-sets/rules. + This function returns the list of rule-sets. :param item: config dictionary. - :param type: rule_sets/rule/r_list. :return: list of rule-sets/rules. """ rs_list = [] + type = "rule_sets" r_sets = item[type] if r_sets: for rs in r_sets: rs_list.append(rs) return rs_list def _compute_command( self, - afi, - name=None, + rs_id, number=None, attrib=None, value=None, remove=False, opr=True, ): """ This function construct the add/delete command based on passed attributes. - :param afi: address type. - :param name: rule-set name. + :param rs_id: rule-set identifier. :param number: rule-number. :param attrib: attribute name. :param value: value. :param remove: True if delete command needed to be construct. - :param opr: opeeration flag. + :param opr: operation flag. :return: generated command. """ + if rs_id["name"] and rs_id["filter"]: + raise ValueError("name and filter cannot be used together") if remove or not opr: - cmd = "delete firewall " + self._get_fw_type(afi) + cmd = "delete firewall " + self._get_fw_type(rs_id["afi"]) else: - cmd = "set firewall " + self._get_fw_type(afi) - if name: - cmd += " " + name + cmd = "set firewall " + self._get_fw_type(rs_id["afi"]) + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + if rs_id["name"]: + cmd += " name " + rs_id["name"] + elif rs_id["filter"]: + cmd += " " + rs_id["filter"] + " filter" + elif rs_id["name"]: + cmd += " " + rs_id["name"] if number: cmd += " rule " + str(number) if attrib: - cmd += " " + attrib.replace("_", "-") + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4") and attrib == "enable_default_log": + cmd += " " + "default-log" + else: + cmd += " " + attrib.replace("_", "-") if value and opr and attrib != "enable_default_log" and attrib != "disable": cmd += " '" + str(value) + "'" return cmd - def _add_r_base_attrib(self, afi, name, attr, rule, opr=True): + def _add_r_base_attrib(self, rs_id, attr, rule, opr=True): """ This function forms the command for 'rules' attributes which doesn't have further sub attributes. - :param afi: address type. - :param name: rule-set name + :param rs_id: rule-set identifier. :param attrib: attribute name :param rule: rule config dictionary. :param opr: True/False. :return: generated command. """ if attr == "number": - command = self._compute_command(afi=afi, name=name, number=rule["number"], opr=opr) + command = self._compute_command(rs_id, number=rule["number"], opr=opr) else: command = self._compute_command( - afi=afi, - name=name, + rs_id=rs_id, number=rule["number"], attrib=attr, value=rule[attr], opr=opr, ) return command - def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True): + def _rs_id(self, have, afi, name=None, filter=None): """ + This function returns the rule-set identifier based on + the example rule, overriding the components as specified. - This function forms the command for 'rule-sets' attributes which doesn't - have further sub attributes. + :param have: example rule. :param afi: address type. - :param name: rule-set name + :param name: rule-set name. + :param filter: filter name. + :return: rule-set identifier. + """ + identifier = {"name": None, "filter": None} + if afi: + identifier["afi"] = afi + else: + raise ValueError("afi must be provided") + + if name: + identifier["name"] = name + return identifier + elif filter: + identifier["filter"] = filter + return identifier + if have: + if "name" in have and have["name"]: + identifier["name"] = have["name"] + return identifier + if "filter" in have and have["filter"]: + identifier["filter"] = have["filter"] + return identifier + # raise ValueError("name or filter must be provided or present in have") + # unless we want a wildcard + return identifier + + def _add_rs_base_attrib(self, rs_id, attrib, rule, opr=True): + """ + + This function forms the command for 'rule-sets' attributes which don't + have further sub attributes. + + :param rs_id: rule-set identifier. :param attrib: attribute name :param rule: rule config dictionary. :param opr: True/False. :return: generated command. """ command = self._compute_command( - afi=afi, - name=name, + rs_id=rs_id, attrib=attrib, value=rule[attrib], opr=opr, ) return command def _bool_to_str(self, val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return "enable" if val else "disable" def _get_fw_type(self, afi): """ This function returns the firewall rule-set type based on IP address. :param afi: address type :return: rule-set type. """ + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + return "ipv6" if afi == "ipv6" else "ipv4" return "ipv6-name" if afi == "ipv6" else "name" def _is_del(self, l_set, h, key="number"): """ This function checks whether rule needs to be deleted based on the rule number. :param l_set: attribute set. :param h: target config. :param key: number. :return: True/False. """ return key in l_set and not (h and self._in_target(h, key)) def _is_w_same(self, w, h, key): """ This function checks whether the key value is same in base and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(self, h, key): """ - This function checks whether the target nexist and key present in target config. + This function checks whether the target exists and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False - - def _is_base_attrib(self, key): - """ - This function checks whether key is present in predefined - based attribute set. - :param key: - :return: True/False. - """ - r_set = ( - "p2p", - "ipsec", - "log", - "action", - "fragment", - "protocol", - "disable", - "description", - "mac_address", - "default_action", - "enable_default_log", - ) - return True if key in r_set else False - - def _get_os_version(self): - os_version = "1.2" - if self._connection: - os_version = self._connection.get_device_info()["network_os_version"] - return os_version diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py index 731014cd..62e4f922 100644 --- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py @@ -1,334 +1,334 @@ # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos_interfaces class It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to it's desired end-state is created """ from __future__ import absolute_import, division, print_function __metaclass__ = type from copy import deepcopy from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( dict_diff, remove_empties, to_list, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( dict_delete, get_interface_type, search_obj_in_list, ) class Interfaces(ConfigBase): """ The vyos_interfaces class """ gather_subset = [ "!all", "!min", ] gather_network_resources = ["interfaces"] def __init__(self, module): super(Interfaces, self).__init__(module) def get_interfaces_facts(self, data=None): """Get the 'facts' (the current configuration) :rtype: A dictionary :returns: The current configuration as a dictionary """ facts, _warnings = Facts(self._module).get_facts( self.gather_subset, self.gather_network_resources, data=data, ) interfaces_facts = facts["ansible_network_resources"].get("interfaces") if not interfaces_facts: return [] return interfaces_facts def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ result = {"changed": False} commands = list() warnings = list() if self.state in self.ACTION_STATES: existing_interfaces_facts = self.get_interfaces_facts() else: existing_interfaces_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_interfaces_facts)) if commands and self.state in self.ACTION_STATES: if not self._module.check_mode: self._connection.edit_config(commands) result["changed"] = True if self.state in self.ACTION_STATES: result["commands"] = commands if self.state in self.ACTION_STATES or self.state == "gathered": changed_interfaces_facts = self.get_interfaces_facts() elif self.state == "rendered": result["rendered"] = commands elif self.state == "parsed": running_config = self._module.params["running_config"] if not running_config: self._module.fail_json( msg="value of running_config parameter must not be empty for state parsed", ) result["parsed"] = self.get_interfaces_facts(data=running_config) else: changed_interfaces_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_interfaces_facts if result["changed"]: result["after"] = changed_interfaces_facts elif self.state == "gathered": result["gathered"] = changed_interfaces_facts result["warnings"] = warnings return result def set_config(self, existing_interfaces_facts): """Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ want = self._module.params["config"] have = existing_interfaces_facts resp = self.set_state(want, have) return to_list(resp) def set_state(self, want, have): """Select the appropriate function based on the state provided :param want: the desired configuration as a dictionary :param have: the current configuration as a dictionary :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] if self.state in ("merged", "replaced", "overridden", "rendered") and not want: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format(self.state), ) if self.state == "overridden": commands.extend(self._state_overridden(want=want, have=have)) elif self.state == "deleted": if not want: for intf in have: commands.extend(self._state_deleted({"name": intf["name"]}, intf)) else: for item in want: obj_in_have = search_obj_in_list(item["name"], have) commands.extend(self._state_deleted(item, obj_in_have)) else: for item in want: name = item["name"] enable_state = item["enabled"] obj_in_have = search_obj_in_list(name, have) if not obj_in_have: obj_in_have = {"name": name, "enabled": enable_state} if self.state in ("merged", "rendered"): commands.extend(self._state_merged(item, obj_in_have)) elif self.state == "replaced": commands.extend(self._state_replaced(item, obj_in_have)) return commands def _state_replaced(self, want, have): """The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] if have: commands.extend(self._state_deleted(want, have)) commands.extend(self._state_merged(want, have)) return commands def _state_overridden(self, want, have): """The command generator when state is overridden :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] for intf in have: intf_in_want = search_obj_in_list(intf["name"], want) if not intf_in_want: commands.extend(self._state_deleted({"name": intf["name"]}, intf)) for intf in want: intf_in_have = search_obj_in_list(intf["name"], have) if not intf_in_have: intf_in_have = { "name": intf["name"], "enabled": intf["enabled"], } commands.extend(self._state_replaced(intf, intf_in_have)) return commands def _state_merged(self, want, have): """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] want_copy = deepcopy(remove_empties(want)) have_copy = deepcopy(have) want_vifs = want_copy.pop("vifs", []) have_vifs = have_copy.pop("vifs", []) updates = dict_diff(have_copy, want_copy) if updates: for key, value in iteritems(updates): commands.append( self._compute_commands(key=key, value=value, interface=want_copy["name"]), ) if want_vifs: for want_vif in want_vifs: have_vif = search_obj_in_list(want_vif["vlan_id"], have_vifs, key="vlan_id") if not have_vif: have_vif = { "vlan_id": want_vif["vlan_id"], "enabled": True, } vif_updates = dict_diff(have_vif, want_vif) if vif_updates: for key, value in iteritems(vif_updates): commands.append( self._compute_commands( key=key, value=value, interface=want_copy["name"], vif=want_vif["vlan_id"], ), ) return commands def _state_deleted(self, want, have): """The command generator when state is deleted :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ commands = [] want_copy = deepcopy(remove_empties(want)) have_copy = deepcopy(have) want_vifs = want_copy.pop("vifs", []) have_vifs = have_copy.pop("vifs", []) for key in dict_delete(have_copy, want_copy).keys(): if key == "enabled": continue commands.append( self._compute_commands(key=key, interface=want_copy["name"], remove=True), ) - if have_copy["enabled"] is False: + if have_copy["enabled"] is False and not ('enabled' in want_copy and want_copy["enabled"] is False): commands.append( self._compute_commands(key="enabled", value=True, interface=want_copy["name"]), ) if have_vifs: for have_vif in have_vifs: want_vif = search_obj_in_list(have_vif["vlan_id"], want_vifs, key="vlan_id") if not want_vif: want_vif = { "vlan_id": have_vif["vlan_id"], "enabled": True, } for key in dict_delete(have_vif, want_vif).keys(): if key == "enabled": continue commands.append( self._compute_commands( key=key, interface=want_copy["name"], vif=want_vif["vlan_id"], remove=True, ), ) if have_vif["enabled"] is False: commands.append( self._compute_commands( key="enabled", value=True, interface=want_copy["name"], vif=want_vif["vlan_id"], ), ) return commands def _compute_commands(self, interface, key, vif=None, value=None, remove=False): intf_context = "interfaces {0} {1}".format(get_interface_type(interface), interface) set_cmd = "set {0}".format(intf_context) del_cmd = "delete {0}".format(intf_context) if vif: set_cmd = set_cmd + (" vif {0}".format(vif)) del_cmd = del_cmd + (" vif {0}".format(vif)) if key == "enabled": if not value: command = "{0} disable".format(set_cmd) else: command = "{0} disable".format(del_cmd) else: if not remove: command = "{0} {1} '{2}'".format(set_cmd, key, value) else: command = "{0} {1}".format(del_cmd, key) return command diff --git a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py index a7652a6b..51b47494 100644 --- a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py +++ b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py @@ -1,170 +1,196 @@ # # -*- coding: utf-8 -*- # Copyright 2020 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos_ospf_interfaces config file. It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to its desired end-state is created. """ from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( ResourceModule, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( dict_merge, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import ( - Ospf_interfacesTemplate, + Ospf_interfacesTemplate ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import ( + Ospf_interfacesTemplate14 +) + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Ospf_interfaces(ResourceModule): """ The vyos_ospf_interfaces config class """ def __init__(self, module): super(Ospf_interfaces, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="ospf_interfaces", tmplt=Ospf_interfacesTemplate(), ) self.parsers = [ "authentication_password", "authentication_md5", "bandwidth", "cost", "hello_interval", "dead_interval", "mtu_ignore", "network", "priority", "retransmit_interval", "transmit_delay", "ifmtu", "instance", "passive", ] + def _validate_template(self): + version = get_os_version(self._module) + if LooseVersion(version) >= LooseVersion("1.4"): + self._tmplt = Ospf_interfacesTemplate14() + else: + self._tmplt = Ospf_interfacesTemplate() + + def parse(self): + """ override parse to check template """ + self._validate_template() + return super().parse() + + def get_parser(self, name): + """get_parsers""" + self._validate_template() + return super().get_parser(name) + def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ + self._validate_template() if self.state not in ["parsed", "gathered"]: self.generate_commands() self.run_commands() return self.result def generate_commands(self): """Generate configuration commands to send based on want, have and desired state. """ wantd = {} haved = {} for entry in self.want: wantd.update({entry["name"]: entry}) for entry in self.have: haved.update({entry["name"]: entry}) # turn all lists of dicts into dicts prior to merge for entry in wantd, haved: self._ospf_int_list_to_dict(entry) # if state is merged, merge want onto have and then compare if self.state == "merged": wantd = dict_merge(haved, wantd) # if state is deleted, empty out wantd and set haved to wantd if self.state == "deleted": h_del = {} for k, v in iteritems(haved): if k in wantd or not wantd: h_del.update({k: v}) haved = h_del have_int = [] for k, have in iteritems(haved): if k in wantd: have_int.append(k) self._remove_ospf_int(have) wantd = {} if self.state == "overridden": have_int = [] for k, have in iteritems(haved): if k not in wantd: have_int.append(k) self._remove_ospf_int(have) # remove superfluous config for overridden and deleted if self.state in ["overridden", "deleted"]: # removing the interfaces from haved that are already negated for interface in have_int: haved.pop(interface) for k, have in iteritems(haved): if k not in wantd: self._compare(want={}, have=have) for k, want in iteritems(wantd): self._compare(want=want, have=haved.pop(k, {})) def _remove_ospf_int(self, entry): int_name = entry.get("name", {}) int_addr = entry.get("address_family", {}) for k, addr in iteritems(int_addr): rem_entry = {"name": int_name, "address_family": {"afi": k}} self.addcmd(rem_entry, "ip_ospf", True) def _compare(self, want, have): """Leverages the base class `compare()` method and populates the list of commands to be run by comparing the `want` and `have` data with the `parsers` defined for the Ospf_interfaces network resource. """ self._compare_addr_family(want=want, have=have) def _compare_addr_family(self, want, have): wdict = want.get("address_family", {}) hdict = have.get("address_family", {}) wname = want.get("name") hname = have.get("name") for name, entry in iteritems(wdict): for key, param in iteritems(entry): w_addr = {"afi": name, key: param} h_addr = {} if hdict.get(name): h_addr = {"afi": name, key: hdict[name].pop(key, {})} w = {"name": wname, "address_family": w_addr} h = {"name": hname, "address_family": h_addr} self.compare(parsers=self.parsers, want=w, have=h) for name, entry in iteritems(hdict): for key, param in iteritems(entry): h_addr = {"afi": name, key: param} w_addr = {} w = {"name": wname, "address_family": w_addr} h = {"name": hname, "address_family": h_addr} self.compare(parsers=self.parsers, want=w, have=h) def _ospf_int_list_to_dict(self, entry): for name, family in iteritems(entry): if "address_family" in family: addr_dict = {} for entry in family.get("address_family", []): addr_dict.update({entry["afi"]: entry}) family["address_family"] = addr_dict self._ospf_int_list_to_dict(family["address_family"]) diff --git a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py index 5b472222..3f4da3ea 100644 --- a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py +++ b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py @@ -1,404 +1,402 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos firewall_global fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ from __future__ import absolute_import, division, print_function __metaclass__ = type from copy import deepcopy from re import M, findall, search from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_global.firewall_global import ( Firewall_globalArgs, ) class Firewall_globalFacts(object): """The vyos firewall_global fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Firewall_globalArgs.argument_spec spec = deepcopy(self.argument_spec) if subspec: if options: facts_argument_spec = spec[subspec][options] else: facts_argument_spec = spec[subspec] else: facts_argument_spec = spec self.generated_spec = utils.generate_dict(facts_argument_spec) def get_device_data(self, connection): return connection.get_config() def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for firewall_global :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead data = self.get_device_data(connection) objs = {} firewalls = findall(r"^set firewall .*$", data, M) if firewalls: objs = self.render_config(firewalls) facts = {} params = utils.validate_config(self.argument_spec, {"config": objs}) facts["firewall_global"] = utils.remove_empties(params["config"]) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def render_config(self, conf): """ Render config as dictionary structure and delete keys from spec for null values :param spec: The facts tree, generated from the argspec :param conf: The configuration :rtype: dictionary :returns: The generated config """ conf = "\n".join( filter( lambda x: ("firewall ipv6-name" and "firewall name" not in x), conf, ), ) a_lst = [ "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ] firewall = self.parse_attr(conf, a_lst) f_sub = { "ping": self.parse_ping(conf), "group": self.parse_group(conf), "route_redirects": self.route_redirects(conf), "state_policy": self.parse_state_policy(conf), } firewall.update(f_sub) return firewall def route_redirects(self, conf): """ This function forms the regex to fetch the afi and invoke functions to fetch route redirects and source routes :param conf: configuration data. :return: generated rule list configuration. """ rr_lst = [] v6_attr = findall( r"^set firewall (?:ipv6-src-route|ipv6-receive-redirects) (\S+)", conf, M, ) if v6_attr: obj = self.parse_rr_attrib(conf, "ipv6") if obj: rr_lst.append(obj) v4_attr = findall( r"^set firewall (?:ip-src-route|receive-redirects|send-redirects) (\S+)", conf, M, ) if v4_attr: obj = self.parse_rr_attrib(conf, "ipv4") if obj: rr_lst.append(obj) return rr_lst def parse_rr_attrib(self, conf, attrib=None): """ This function fetches the 'ip_src_route' invoke function to parse icmp redirects. :param conf: configuration to be parsed. :param attrib: 'ipv4/ipv6'. :return: generated config dictionary. """ cfg_dict = self.parse_attr(conf, ["ip_src_route"], type=attrib) cfg_dict["icmp_redirects"] = self.parse_icmp_redirects(conf, attrib) cfg_dict["afi"] = attrib return cfg_dict def parse_icmp_redirects(self, conf, attrib=None): """ This function triggers the parsing of 'icmp_redirects' attributes. :param conf: configuration to be parsed. :param attrib: 'ipv4/ipv6'. :return: generated config dictionary. """ a_lst = ["send", "receive"] cfg_dict = self.parse_attr(conf, a_lst, type=attrib) return cfg_dict def parse_ping(self, conf): """ This function triggers the parsing of 'ping' attributes. :param conf: configuration to be parsed. :return: generated config dictionary. """ a_lst = ["all", "broadcast"] cfg_dict = self.parse_attr(conf, a_lst) return cfg_dict def parse_state_policy(self, conf): """ This function fetched the connecton type and invoke function to parse other state-policy attributes. :param conf: configuration data. :return: generated rule list configuration. """ sp_lst = [] - attrib = "state-policy" - policies = findall(r"^set firewall " + attrib + " (\\S+)", conf, M) - + policies = findall(r"^set firewall (?:global-options )state-policy (\S+)", conf, M) + policies = list(set(policies)) # remove redundancies if policies: rules_lst = [] for sp in set(policies): sp_regex = r" %s .+$" % sp cfg = "\n".join(findall(sp_regex, conf, M)) obj = self.parse_policies(cfg, sp) obj["connection_type"] = sp if obj: rules_lst.append(obj) sp_lst = sorted(rules_lst, key=lambda i: i["connection_type"]) return sp_lst def parse_policies(self, conf, attrib=None): """ This function triggers the parsing of policy attributes action and log. :param conf: configuration :param attrib: connection type. :return: generated rule configuration dictionary. """ - a_lst = ["action", "log"] + a_lst = ["action", "log", "log_level"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_group(self, conf): """ This function triggers the parsing of 'group' attributes. :param conf: configuration. :return: generated config dictionary. """ cfg_dict = {} cfg_dict["port_group"] = self.parse_group_lst(conf, "port-group", False) cfg_dict["address_group"] = self.parse_group_lst( conf, "address-group", ) + self.parse_group_lst(conf, "ipv6-address-group") cfg_dict["network_group"] = self.parse_group_lst( conf, "network-group", ) + self.parse_group_lst(conf, "ipv6-network-group") return cfg_dict def parse_group_lst(self, conf, type, include_afi=True): """ This function fetches the name of group and invoke function to parse group attributes'. :param conf: configuration data. :param type: type of group. :param include_afi: if the afi should be included in the parsed object :return: generated group list configuration. """ g_lst = [] groups = findall(r"^set firewall group " + type + " (\\S+)", conf, M) if groups: rules_lst = [] for gr in set(groups): gr_regex = r" %s .+$" % gr cfg = "\n".join(findall(gr_regex, conf, M)) if "ipv6" in type: # fmt: off obj = self.parse_groups(cfg, type[len("ipv6-"):], gr) # fmt: on if include_afi: obj["afi"] = "ipv6" else: obj = self.parse_groups(cfg, type, gr) if include_afi: obj["afi"] = "ipv4" obj["name"] = gr.strip("'") if obj: rules_lst.append(obj) g_lst = sorted(rules_lst, key=lambda i: i["name"]) return g_lst def parse_groups(self, conf, type, name): """ This function fetches the description and invoke the parsing of group members. :param conf: configuration. :param type: type of group. :param name: name of group. :return: generated configuration dictionary. """ a_lst = ["name", "description"] group = self.parse_attr(conf, a_lst) key = self.get_key(type) r_sub = {key[0]: self.parse_address_port_lst(conf, name, key[1])} group.update(r_sub) return group def parse_address_port_lst(self, conf, name, key): """ This function forms the regex to fetch the group members attributes. :param conf: configuration data. :param name: name of group. :param key: key value. :return: generated member list configuration. """ l_lst = [] attribs = findall(r"^.*" + name + " " + key + " (\\S+)", conf, M) if attribs: for attr in attribs: if key == "port": l_lst.append({"port": attr.strip("'")}) else: l_lst.append({"address": attr.strip("'")}) return l_lst def parse_attr(self, conf, attr_list, match=None, type=None): """ This function peforms the following: - Form the regex to fetch the required attribute config. - Type cast the output in desired format. :param conf: configuration. :param attr_list: list of attributes. :param match: parent node/attribute name. :return: generated config dictionary. """ config = {} for attrib in attr_list: regex = self.map_regex(attrib, type) if match: regex = match + " " + regex if conf: if self.is_bool(attrib): - attr = self.map_regex(attrib, type) - out = conf.find(attr.replace("_", "-")) - dis = conf.find(attr.replace("_", "-") + " 'disable'") - if out >= 1: - if dis >= 1: + # fancy regex to make sure we don't get a substring + out = search(r"^.*" + regex + r"( 'disable')?(?=\s|$)", conf, M) + if out: + if out.group(1): config[attrib] = False else: config[attrib] = True else: - out = search(r"^.*" + regex + " (.+)", conf, M) + out = search(r"^.*" + regex + r" (.+)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): val = int(val) config[attrib] = val return config def get_key(self, type): """ This function map the group type to member type :param type: :return: """ key = () if type == "port-group": key = ("members", "port") elif type == "address-group": key = ("members", "address") elif type == "network-group": key = ("members", "network") return key def map_regex(self, attrib, type=None): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ regex = attrib.replace("_", "-") if attrib == "all": regex = "all-ping" elif attrib == "disabled": regex = "disable" elif attrib == "broadcast": regex = "broadcast-ping" elif attrib == "send": if type == "ipv6": regex = "ipv6-send-redirects" else: regex = "send-redirects" elif attrib == "ip_src_route": if type == "ipv6": regex = "ipv6-src-route" elif attrib == "receive": if type == "ipv6": regex = "ipv6-receive-redirects" else: regex = "receive-redirects" return regex def is_num(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ num_set = ("time", "code", "type", "count", "burst", "number") return True if attrib in num_set else False def get_src_route(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ return "ipv6_src_route" if attrib == "ipv6" else "ip_src_route" def is_bool(self, attrib): """ This function looks for the attribute in predefined bool type set. :param attrib: attribute. :return: True/False """ bool_set = ( "all", "log", "send", "receive", "broadcast", "config_trap", "log_martians", "syn_cookies", "ip_src_route", "twa_hazards_protection", ) return True if attrib in bool_set else False diff --git a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py index ead038a5..1fc70255 100644 --- a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py @@ -1,388 +1,534 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos firewall_rules fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ from __future__ import absolute_import, division, print_function __metaclass__ = type -import re - from copy import deepcopy from re import M, findall, search from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import ( Firewall_rulesArgs, ) class Firewall_rulesFacts(object): """The vyos firewall_rules fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Firewall_rulesArgs.argument_spec spec = deepcopy(self.argument_spec) if subspec: if options: facts_argument_spec = spec[subspec][options] else: facts_argument_spec = spec[subspec] else: facts_argument_spec = spec self.generated_spec = utils.generate_dict(facts_argument_spec) def get_device_data(self, connection): return connection.get_config() def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for firewall_rules :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead data = self.get_device_data(connection) # split the config into instances of the resource objs = [] - v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M) - v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M) + # check 1.4+ first + new_rules = True + v6_rules = findall(r"^set firewall ipv6 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M) + if not v6_rules: + v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M) + if v6_rules: + new_rules = False + v4_rules = findall(r"^set firewall ipv4 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M) + if not v4_rules: + v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M) + if v4_rules: + new_rules = False if v6_rules: - config = self.get_rules(data, v6_rules, type="ipv6") + if new_rules: + config = self.get_rules_post_1_4(data, v6_rules, type="ipv6") + else: + config = self.get_rules(data, v6_rules, type="ipv6") if config: config = utils.remove_empties(config) objs.append(config) if v4_rules: - config = self.get_rules(data, v4_rules, type="ipv4") + if new_rules: + config = self.get_rules_post_1_4(data, v4_rules, type="ipv4") + else: + config = self.get_rules(data, v4_rules, type="ipv4") if config: config = utils.remove_empties(config) objs.append(config) ansible_facts["ansible_network_resources"].pop("firewall_rules", None) facts = {} if objs: facts["firewall_rules"] = [] params = utils.validate_config(self.argument_spec, {"config": objs}) for cfg in params["config"]: facts["firewall_rules"].append(utils.remove_empties(cfg)) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def get_rules(self, data, rules, type): """ This function performs following: - Form regex to fetch 'rule-sets' specific config from data. - Form the rule-set list based on ip address. :param data: configuration. :param rules: list of rule-sets. :param type: ip address type. :return: generated rule-sets configuration. """ r_v4 = [] r_v6 = [] for r in set(rules): name_key = "ipv6-name" if type == "ipv6" else "name" rule_regex = r" %s %s .+$" % (name_key, r.strip("'")) cfg = findall(rule_regex, data, M) fr = self.render_config(cfg, r.strip("'")) fr["name"] = r.strip("'") if type == "ipv6": r_v6.append(fr) else: r_v4.append(fr) if r_v4: config = {"afi": "ipv4", "rule_sets": r_v4} if r_v6: config = {"afi": "ipv6", "rule_sets": r_v6} return config + def get_rules_post_1_4(self, data, rules, type): + """ + This function performs following: + - Form regex to fetch 'rule-sets' specific config from data. + - Form the rule-set list based on ip address. + Specifically for v1.4+ version. + :param data: configuration. + :param rules: list of rule-sets. + :param type: ip address type. + :return: generated rule-sets configuration. + """ + r_v4 = [] + r_v6 = [] + for kind, name in set(rules): + rule_regex = r" %s %s %s .+$" % (type, kind, name.strip("'")) + cfg = findall(rule_regex, data, M) + fr = self.render_config(cfg, name.strip("'")) + if kind == "name": + fr["name"] = name.strip("'") + elif kind in ("forward", "input", "output"): + fr["filter"] = kind + else: + raise ValueError("Unknown rule kind: %s %s" % kind, name) + if type == "ipv6": + r_v6.append(fr) + else: + r_v4.append(fr) + if r_v4: + config = {"afi": "ipv4", "rule_sets": r_v4} + if r_v6: + config = {"afi": "ipv6", "rule_sets": r_v6} + return config + def render_config(self, conf, match): """ Render config as dictionary structure and delete keys from spec for null values :param spec: The facts tree, generated from the argspec :param conf: The configuration :rtype: dictionary :returns: The generated config """ conf = "\n".join(filter(lambda x: x, conf)) - a_lst = ["description", "default_action", "enable_default_log"] + a_lst = ["description", "default_action", "default_jump_target", "enable_default_log", "default_log"] config = self.parse_attr(conf, a_lst, match) if not config: config = {} + if 'default_log' in config: + config['enable_default_log'] = config.pop('default_log') config["rules"] = self.parse_rules_lst(conf) return config def parse_rules_lst(self, conf): """ This function forms the regex to fetch the 'rules' with in 'rule-sets' :param conf: configuration data. :return: generated rule list configuration. """ r_lst = [] rules = findall(r"rule (?:\'*)(\d+)(?:\'*)", conf, M) if rules: rules_lst = [] for r in set(rules): r_regex = r" %s .+$" % r cfg = "\n".join(findall(r_regex, conf, M)) obj = self.parse_rules(cfg) obj["number"] = int(r) if obj: rules_lst.append(obj) r_lst = sorted(rules_lst, key=lambda i: i["number"]) return r_lst def parse_rules(self, conf): """ This function triggers the parsing of 'rule' attributes. a_lst is a list having rule attributes which doesn't have further sub attributes. :param conf: configuration :return: generated rule configuration dictionary. """ a_lst = [ "ipsec", "log", "action", "protocol", "fragment", "disable", "description", "icmp", + "jump_target", + "queue", + "queue_options", ] rule = self.parse_attr(conf, a_lst) r_sub = { "p2p": self.parse_p2p(conf), - "tcp": self.parse_tcp(conf, "tcp"), + "tcp": self.parse_tcp(conf), "icmp": self.parse_icmp(conf, "icmp"), "time": self.parse_time(conf, "time"), "limit": self.parse_limit(conf, "limit"), "state": self.parse_state(conf, "state"), "recent": self.parse_recent(conf, "recent"), "source": self.parse_src_or_dest(conf, "source"), "destination": self.parse_src_or_dest(conf, "destination"), + "inbound_interface": self.parse_interface(conf, "inbound-interface"), + "outbound_interface": self.parse_interface(conf, "outbound-interface"), + "packet_length": self.parse_packet_length(conf, "packet-length"), + "packet_length_exclude": self.parse_packet_length(conf, "packet-length-exclude"), } rule.update(r_sub) return rule + def parse_interface(self, conf, attrib): + """ + This function triggers the parsing of 'interface' attributes. + :param conf: configuration. + :param attrib: 'interface'. + :return: generated config dictionary. + """ + a_lst = ["name", "group"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_packet_length(self, conf, attrib=None): + """ + This function triggers the parsing of 'packet-length' attributes. + :param conf: configuration. + :param attrib: 'packet-length'. + :return: generated config dictionary. + """ + lengths = [] + rule_regex = r"%s (\d+)" % attrib + found_lengths = findall(rule_regex, conf, M) + if found_lengths: + lengths = [] + for l in set(found_lengths): + obj = {"length": l.strip("'")} + lengths.append(obj) + return lengths + def parse_p2p(self, conf): """ This function forms the regex to fetch the 'p2p' with in 'rules' :param conf: configuration data. :return: generated rule list configuration. """ a_lst = [] applications = findall(r"p2p (?:\'*)(\d+)(?:\'*)", conf, M) if applications: app_lst = [] for r in set(applications): obj = {"application": r.strip("'")} app_lst.append(obj) a_lst = sorted(app_lst, key=lambda i: i["application"]) return a_lst def parse_src_or_dest(self, conf, attrib=None): """ This function triggers the parsing of 'source or destination' attributes. :param conf: configuration. :param attrib:'source/destination'. :return:generated source/destination configuration dictionary. """ a_lst = ["port", "address", "mac_address"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) cfg_dict["group"] = self.parse_group(conf, attrib + " group") return cfg_dict def parse_recent(self, conf, attrib=None): """ This function triggers the parsing of 'recent' attributes :param conf: configuration. :param attrib: 'recent'. :return: generated config dictionary. """ a_lst = ["time", "count"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict - def parse_tcp(self, conf, attrib=None): + def parse_tcp(self, conf): """ This function triggers the parsing of 'tcp' attributes. :param conf: configuration. :param attrib: 'tcp'. :return: generated config dictionary. """ - cfg_dict = self.parse_attr(conf, ["flags"], match=attrib) - return cfg_dict + f_lst = [] + flags = findall(r"tcp flags (not )?(?:\'*)([\w!,]+)(?:\'*)", conf, M) + # for pre 1.4, this is a string including possible commas + # and ! as an inverter. For 1.4+ this is a single flag per + # command and 'not' as the inverter + if flags: + flag_lst = [] + for n, f in set(flags): + f = f.strip("'").lower() + if "," in f: + # pre 1.4 version with multiple flags + fs = f.split(",") + for f in fs: + if "!" in f: + obj = {"flag": f.strip("'!"), "invert": True} + else: + obj = {"flag": f.strip("'")} + flag_lst.append(obj) + elif "!" in f: + obj = {"flag": f.strip("'!"), "invert": True} + flag_lst.append(obj) + else: + obj = {"flag": f.strip("'")} + if n: + obj["invert"] = True + flag_lst.append(obj) + f_lst = sorted(flag_lst, key=lambda i: i["flag"]) + return {"flags": f_lst} def parse_time(self, conf, attrib=None): """ This function triggers the parsing of 'time' attributes. :param conf: configuration. :param attrib: 'time'. :return: generated config dictionary. """ a_lst = [ "stopdate", "stoptime", "weekdays", "monthdays", "startdate", "starttime", ] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_state(self, conf, attrib=None): """ This function triggers the parsing of 'state' attributes. :param conf: configuration :param attrib: 'state'. :return: generated config dictionary. """ a_lst = ["new", "invalid", "related", "established"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_group(self, conf, attrib=None): """ This function triggers the parsing of 'group' attributes. :param conf: configuration. :param attrib: 'group'. :return: generated config dictionary. """ a_lst = ["port_group", "address_group", "network_group"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict + def parse_icmp_attr(self, conf, match): + """ + This function peforms the following: + - parse ICMP arguemnts for firewall rules + - consider that older versions may need numbers or letters + in type, newer ones are more specific + :param conf: configuration. + :param match: parent node/attribute name. + :return: generated config dictionary. + """ + config = {} + if not conf: + return config + + for attrib in ("code", "type", "type-name"): + regex = self.map_regex(attrib) + if match: + regex = match + " " + regex + out = search(r"^.*" + regex + " (.+)", conf, M) + if out: + val = out.group(1).strip("'") + if attrib == 'type-name': + config['type_name'] = val + if attrib == 'code': + config['code'] = int(val) + if attrib == 'type': + # <1.3 could be # (type), #/# (type/code) or 'type' (type_name) + # recent this is only for strings + if "/" in val: # type/code + (type_no, code) = val.split(".") + config['type'] = type_no + config['code'] = code + elif val.isnumeric(): + config['type'] = type_no + else: + config['type_name'] = val + return config + def parse_icmp(self, conf, attrib=None): """ This function triggers the parsing of 'icmp' attributes. :param conf: configuration to be parsed. :param attrib: 'icmp'. :return: generated config dictionary. """ - a_lst = ["code", "type", "type_name"] - if attrib == "icmp": - attrib = "icmpv6" - conf = re.sub("icmpv6 type", "icmpv6 type-name", conf) - cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + cfg_dict = self.parse_icmp_attr(conf, "icmp") + if (len(cfg_dict) == 0): + cfg_dict = self.parse_icmp_attr(conf, "icmpv6") return cfg_dict def parse_limit(self, conf, attrib=None): """ This function triggers the parsing of 'limit' attributes. :param conf: configuration to be parsed. :param attrib: 'limit' :return: generated config dictionary. """ cfg_dict = self.parse_attr(conf, ["burst"], match=attrib) cfg_dict["rate"] = self.parse_rate(conf, "rate") return cfg_dict def parse_rate(self, conf, attrib=None): """ This function triggers the parsing of 'rate' attributes. :param conf: configuration. :param attrib: 'rate' :return: generated config dictionary. """ a_lst = ["unit", "number"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_attr(self, conf, attr_list, match=None): """ This function peforms the following: - Form the regex to fetch the required attribute config. - Type cast the output in desired format. :param conf: configuration. :param attr_list: list of attributes. :param match: parent node/attribute name. :return: generated config dictionary. """ config = {} for attrib in attr_list: regex = self.map_regex(attrib) if match: regex = match + " " + regex if conf: if self.is_bool(attrib): out = conf.find(attrib.replace("_", "-")) - dis = conf.find(attrib.replace("_", "-") + " 'disable'") if out >= 1: if dis >= 1: config[attrib] = False else: config[attrib] = True else: out = search(r"^.*" + regex + " (.+)", conf, M) if not out and attrib == "disable": out = search(r"^.*\d+" + " ('disable'$)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): val = int(val) if attrib == "disable": val = True config[attrib] = val return config def map_regex(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ regex = attrib.replace("_", "-") if attrib == "disabled": regex = "disable" return regex def is_bool(self, attrib): """ This function looks for the attribute in predefined bool type set. :param attrib: attribute. :return: True/False """ bool_set = ( "new", "invalid", "related", "disabled", "established", "enable_default_log", + "default_log", ) return True if attrib in bool_set else False def is_num(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ num_set = ("time", "code", "type", "count", "burst", "number") return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py index 995be911..cd8008c6 100644 --- a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py @@ -1,134 +1,146 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos interfaces fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ from __future__ import absolute_import, division, print_function __metaclass__ = type from copy import deepcopy -from re import M, findall +from re import M, findall, search from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.interfaces.interfaces import ( InterfacesArgs, ) class InterfacesFacts(object): """The vyos interfaces fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = InterfacesArgs.argument_spec spec = deepcopy(self.argument_spec) if subspec: if options: facts_argument_spec = spec[subspec][options] else: facts_argument_spec = spec[subspec] else: facts_argument_spec = spec self.generated_spec = utils.generate_dict(facts_argument_spec) def get_device_data(self, connection): data = connection.get_config(flags=["| grep interfaces"]) return data def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for interfaces :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = self.get_device_data(connection) objs = [] interface_names = findall( r"^set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|vxlan|openvpn|wireguard) (?:\'*)(\S+)(?:\'*)", data, M, ) if interface_names: for interface in set(interface_names): - intf_regex = r" %s .+$" % interface.strip("'") + intf_regex = r" %s (.+$)" % interface.strip("'") cfg = findall(intf_regex, data, M) obj = self.render_config(cfg) obj["name"] = interface.strip("'") if obj: objs.append(obj) facts = {} if objs: facts["interfaces"] = [] params = utils.validate_config(self.argument_spec, {"config": objs}) for cfg in params["config"]: facts["interfaces"].append(utils.remove_empties(cfg)) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def render_config(self, conf): """ Render config as dictionary structure and delete keys from spec for null values :param spec: The facts tree, generated from the argspec :param conf: The configuration :rtype: dictionary :returns: The generated config """ vif_conf = "\n".join(filter(lambda x: ("vif" in x), conf)) eth_conf = "\n".join(filter(lambda x: ("vif" not in x), conf)) config = self.parse_attribs(["description", "speed", "mtu", "duplex"], eth_conf) config["vifs"] = self.parse_vifs(vif_conf) return utils.remove_empties(config) def parse_vifs(self, conf): vif_names = findall(r"vif (?:\'*)(\d+)(?:\'*)", conf, M) vifs_list = None if vif_names: vifs_list = [] for vif in set(vif_names): - vif_regex = r" %s .+$" % vif + vif_regex = r"%s (.+$)" % vif cfg = "\n".join(findall(vif_regex, conf, M)) obj = self.parse_attribs(["description", "mtu"], cfg) obj["vlan_id"] = int(vif) if obj: vifs_list.append(obj) vifs_list = sorted(vifs_list, key=lambda i: i["vlan_id"]) return vifs_list def parse_attribs(self, attribs, conf): + """ + Parse the attributes of a network interface. + + :param attribs: List of attribute names. + :param conf: Configuration string. + :rtype: dict + :returns: Parsed configuration dictionary. + """ config = {} for item in attribs: value = utils.parse_conf_arg(conf, item) if value and item == "mtu": config[item] = int(value.strip("'")) elif value: config[item] = value.strip("'") else: config[item] = None - if "disable" in conf: + + # match only on disable next to the interface name + # there are other sub-commands that can be disabled + match = search(r"^ *disable", conf, M) + if match: config["enabled"] = False else: config["enabled"] = True return utils.remove_empties(config) diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py index be467a0a..7d4d1a08 100644 --- a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py @@ -1,146 +1,146 @@ # # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos l3_interfaces fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ from __future__ import absolute_import, division, print_function __metaclass__ = type import re from copy import deepcopy from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.l3_interfaces.l3_interfaces import ( L3_interfacesArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( get_ip_address_version, ) class L3_interfacesFacts(object): """The vyos l3_interfaces fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = L3_interfacesArgs.argument_spec spec = deepcopy(self.argument_spec) if subspec: if options: facts_argument_spec = spec[subspec][options] else: facts_argument_spec = spec[subspec] else: facts_argument_spec = spec self.generated_spec = utils.generate_dict(facts_argument_spec) def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for l3_interfaces :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = connection.get_config() # operate on a collection of resource x objs = [] interface_names = re.findall( - r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|vxlan) (?:\'*)(\S+)(?:\'*)", + r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|vxlan) (?:\'*)(\S+)(?:\'*)", data, re.M, ) if interface_names: for interface in set(interface_names): intf_regex = r" %s .+$" % interface cfg = re.findall(intf_regex, data, re.M) obj = self.render_config(cfg) obj["name"] = interface.strip("'") if obj: objs.append(obj) ansible_facts["ansible_network_resources"].pop("l3_interfaces", None) facts = {} if objs: facts["l3_interfaces"] = [] params = utils.validate_config(self.argument_spec, {"config": objs}) for cfg in params["config"]: facts["l3_interfaces"].append(utils.remove_empties(cfg)) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def render_config(self, conf): """ Render config as dictionary structure and delete keys from spec for null values :param spec: The facts tree, generated from the argspec :param conf: The configuration :rtype: dictionary :returns: The generated config """ vif_conf = "\n".join(filter(lambda x: ("vif" in x), conf)) eth_conf = "\n".join(filter(lambda x: ("vif" not in x), conf)) config = self.parse_attribs(eth_conf) config["vifs"] = self.parse_vifs(vif_conf) return utils.remove_empties(config) def parse_vifs(self, conf): vif_names = re.findall(r"vif (\d+)", conf, re.M) vifs_list = None if vif_names: vifs_list = [] for vif in set(vif_names): vif_regex = r" %s .+$" % vif cfg = "\n".join(re.findall(vif_regex, conf, re.M)) obj = self.parse_attribs(cfg) obj["vlan_id"] = vif if obj: vifs_list.append(obj) return vifs_list def parse_attribs(self, conf): config = {} ipaddrs = re.findall(r"address (\S+)", conf, re.M) config["ipv4"] = [] config["ipv6"] = [] for item in ipaddrs: item = item.strip("'") if item == "dhcp": config["ipv4"].append({"address": item}) elif item == "dhcpv6": config["ipv6"].append({"address": item}) elif item == "no-default-link-local": config["ipv6"].append({"address": item}) elif item == "autoconf": config["ipv6"].append({"address": item}) else: ip_version = get_ip_address_version(item.split("/")[0]) if ip_version == 4: config["ipv4"].append({"address": item}) else: config["ipv6"].append({"address": item}) for key, value in iteritems(config): if value == []: config[key] = None return utils.remove_empties(config) diff --git a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py index af6c5770..c0e74895 100644 --- a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py @@ -1,103 +1,142 @@ # -*- coding: utf-8 -*- # Copyright 2020 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos ospf_interfaces fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospf_interfaces.ospf_interfaces import ( Ospf_interfacesArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import ( - Ospf_interfacesTemplate, + Ospf_interfacesTemplate ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import ( + Ospf_interfacesTemplate14 +) + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Ospf_interfacesFacts(object): """The vyos ospf_interfaces facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Ospf_interfacesArgs.argument_spec def get_device_data(self, connection): + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + # use set protocols ospf in order to get both ospf and ospfv3 + return connection.get("show configuration commands | match 'set protocols ospf'") return connection.get('show configuration commands | match "set interfaces"') - def get_config_set(self, data): + def get_config_set_1_4(self, data): + """To classify the configurations beased on interface""" + interface_list = [] + config_set = [] + int_string = "" + for config_line in data.splitlines(): + ospf_int = re.search(r"set protocols (?:ospf|ospfv3) interface (\S+) .*", config_line) + if ospf_int: + if ospf_int.group(1) not in interface_list: + if int_string: + config_set.append(int_string) + interface_list.append(ospf_int.group(1)) + int_string = "" + int_string = int_string + config_line + "\n" + if int_string: + config_set.append(int_string) + return config_set + + def get_config_set_1_2(self, data): """To classify the configurations beased on interface""" interface_list = [] config_set = [] int_string = "" for config_line in data.splitlines(): ospf_int = re.search(r"set interfaces \S+ (\S+) .*", config_line) if ospf_int: if ospf_int.group(1) not in interface_list: if int_string: config_set.append(int_string) interface_list.append(ospf_int.group(1)) int_string = "" int_string = int_string + config_line + "\n" if int_string: config_set.append(int_string) return config_set + def get_config_set(self, data, connection): + """To classify the configurations beased on interface""" + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + return self.get_config_set_1_4(data) + return self.get_config_set_1_2(data) + def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Ospf_interfaces network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = [] - ospf_interfaces_parser = Ospf_interfacesTemplate(lines=[], module=self._module) + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + ospf_interface_class = Ospf_interfacesTemplate14 + else: + ospf_interface_class = Ospf_interfacesTemplate + ospf_interfaces_parser = ospf_interface_class(lines=[], module=self._module) if not data: data = self.get_device_data(connection) # parse native config using the Ospf_interfaces template ospf_interfaces_facts = [] - resources = self.get_config_set(data) + resources = self.get_config_set(data, connection) for resource in resources: - ospf_interfaces_parser = Ospf_interfacesTemplate( + ospf_interfaces_parser = ospf_interface_class( lines=resource.split("\n"), module=self._module, ) objs = ospf_interfaces_parser.parse() for key, sortv in [("address_family", "afi")]: if key in objs and objs[key]: objs[key] = list(objs[key].values()) ospf_interfaces_facts.append(objs) ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None) facts = {"ospf_interfaces": []} params = utils.remove_empties( ospf_interfaces_parser.validate_config( self.argument_spec, {"config": ospf_interfaces_facts}, redact=True, - ), + ) ) if params.get("config"): for cfg in params["config"]: facts["ospf_interfaces"].append(utils.remove_empties(cfg)) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts diff --git a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py new file mode 100644 index 00000000..7f49d47a --- /dev/null +++ b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py @@ -0,0 +1,650 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +""" +The Ospf_interfaces parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _get_parameters(data): + if data["afi"] == "ipv6": + val = ["ospfv3", "ipv6"] + else: + val = ["ospf", "ip"] + return val + + +def _tmplt_ospf_int_delete(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + params[0] + " interface {name}".format(**config_data) + ) + + return command + + +def _tmplt_ospf_int_cost(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " cost {cost}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_auth_password(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " authentication plaintext-password {plaintext_password}".format( + **config_data["address_family"]["authentication"] + ) + ) + return command + + +def _tmplt_ospf_int_auth_md5(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " authentication md5 key-id {key_id} ".format( + **config_data["address_family"]["authentication"]["md5_key"] + ) + + "md5-key {key}".format(**config_data["address_family"]["authentication"]["md5_key"]) + ) + + return command + + +def _tmplt_ospf_int_auth_md5_delete(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " authentication" + ) + + return command + + +def _tmplt_ospf_int_bw(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " bandwidth {bandwidth}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_hello_interval(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " hello-interval {hello_interval}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_dead_interval(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " dead-interval {dead_interval}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_mtu_ignore(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " mtu-ignore" + ) + + return command + + +def _tmplt_ospf_int_network(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " network {network}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_priority(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " priority {priority}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_retransmit_interval(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " retransmit-interval {retransmit_interval}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_transmit_delay(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " transmit-delay {transmit_delay}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_ifmtu(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " ifmtu {ifmtu}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_instance(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " instance-id {instance}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_passive(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " passive" + ) + + return command + + +class Ospf_interfacesTemplate14(NetworkTemplate): + def __init__(self, lines=None, module=None): + prefix = {"set": "set", "remove": "delete"} + super(Ospf_interfacesTemplate14, self).__init__( + lines=lines, tmplt=self, prefix=prefix, module=module + ) + + # fmt: off + PARSERS = [ + { + "name": "ip_ospf", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "remval": _tmplt_ospf_int_delete, + "compval": "address_family", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + } + } + } + }, + { + "name": "authentication_password", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+authentication + \s+plaintext-password + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_auth_password, + "compval": "address_family.authentication", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "authentication": { + "plaintext_password": "{{ text }}" + } + } + } + } + }, + { + "name": "authentication_md5", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+authentication + \s+md5 + \s+key-id + \s+(?P\d+) + \s+md5-key + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_auth_md5, + "remval": _tmplt_ospf_int_auth_md5_delete, + "compval": "address_family.authentication", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "authentication": { + "md5_key": { + "key_id": "{{ id }}", + "key": "{{ text }}" + } + } + } + } + } + }, + { + "name": "bandwidth", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+bandwidth + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_bw, + "compval": "address_family.bandwidth", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "bandwidth": "{{ bw }}" + } + } + } + }, + { + "name": "cost", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+cost + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_cost, + "compval": "address_family.cost", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "cost": "{{ val }}" + } + } + } + }, + { + "name": "hello_interval", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+hello-interval + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_hello_interval, + "compval": "address_family.hello_interval", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "hello_interval": "{{ val }}" + } + } + } + }, + { + "name": "dead_interval", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+dead-interval + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_dead_interval, + "compval": "address_family.dead_interval", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "dead_interval": "{{ val }}" + } + } + } + }, + { + "name": "mtu_ignore", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+(?P\'mtu-ignore\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_mtu_ignore, + "compval": "address_family.mtu_ignore", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "mtu_ignore": "{{ True if mtu is defined }}" + } + } + } + }, + { + "name": "network", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+network + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_network, + "compval": "address_family.network", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "network": "{{ val }}" + } + } + } + }, + { + "name": "priority", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+priority + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_priority, + "compval": "address_family.priority", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "priority": "{{ val }}" + } + } + } + }, + { + "name": "retransmit_interval", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+retransmit-interval + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_retransmit_interval, + "compval": "address_family.retransmit_interval", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "retransmit_interval": "{{ val }}" + } + } + } + }, + { + "name": "transmit_delay", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+transmit-delay + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_transmit_delay, + "compval": "address_family.transmit_delay", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "transmit_delay": "{{ val }}" + } + } + } + }, + { + "name": "ifmtu", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+ifmtu + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_ifmtu, + "compval": "address_family.ifmtu", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "ifmtu": "{{ val }}" + } + } + } + }, + { + "name": "instance", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+instance-id + \s+(?P\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_instance, + "compval": "address_family.instance", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "instance": "{{ val }}" + } + } + } + }, + { + "name": "passive", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + \s+(?P\'passive\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_passive, + "compval": "address_family.passive", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "passive": "{{ True if pass is defined }}" + } + } + } + }, + { + "name": "interface_name", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?Pospf|ospfv3) + \s+interface + \s+(?P\S+) + .*$""", + re.VERBOSE, + ), + "setval": "set protocols {{ proto }} interface {{ name }}", + "result": { + "name": "{{ name }}", + } + }, + ] + # fmt: on diff --git a/plugins/module_utils/network/vyos/utils/version.py b/plugins/module_utils/network/vyos/utils/version.py new file mode 100644 index 00000000..cc3028c3 --- /dev/null +++ b/plugins/module_utils/network/vyos/utils/version.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Felix Fontein +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +"""Provide version object to compare version numbers.""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.compat.version import LooseVersion # pylint: disable=unused-import diff --git a/plugins/module_utils/network/vyos/vyos.py b/plugins/module_utils/network/vyos/vyos.py index 4fcb3317..1430b1b1 100644 --- a/plugins/module_utils/network/vyos/vyos.py +++ b/plugins/module_utils/network/vyos/vyos.py @@ -1,102 +1,108 @@ # This code is part of Ansible, but is an independent component. # This particular file snippet, and this file snippet only, is BSD licensed. # Modules you write using this snippet, which is embedded dynamically by Ansible # still belong to the author of the module, and may assign their own license # to the complete work. # # (c) 2016 Red Hat Inc. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from __future__ import absolute_import, division, print_function __metaclass__ = type import json from ansible.module_utils._text import to_text from ansible.module_utils.connection import Connection, ConnectionError - _DEVICE_CONFIGS = {} def get_connection(module): if hasattr(module, "_vyos_connection"): return module._vyos_connection capabilities = get_capabilities(module) network_api = capabilities.get("network_api") if network_api == "cliconf": module._vyos_connection = Connection(module._socket_path) else: module.fail_json(msg="Invalid connection type %s" % network_api) return module._vyos_connection def get_capabilities(module): if hasattr(module, "_vyos_capabilities"): return module._vyos_capabilities try: capabilities = Connection(module._socket_path).get_capabilities() except ConnectionError as exc: module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) module._vyos_capabilities = json.loads(capabilities) return module._vyos_capabilities def get_config(module, flags=None, format=None): flags = [] if flags is None else flags global _DEVICE_CONFIGS if _DEVICE_CONFIGS != {}: return _DEVICE_CONFIGS else: connection = get_connection(module) try: out = connection.get_config(flags=flags, format=format) except ConnectionError as exc: module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) cfg = to_text(out, errors="surrogate_then_replace").strip() _DEVICE_CONFIGS = cfg return cfg def run_commands(module, commands, check_rc=True): connection = get_connection(module) try: response = connection.run_commands(commands=commands, check_rc=check_rc) except ConnectionError as exc: module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) return response def load_config(module, commands, commit=False, comment=None): connection = get_connection(module) try: response = connection.edit_config(candidate=commands, commit=commit, comment=comment) except ConnectionError as exc: module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) return response.get("diff") + + +def get_os_version(module): + connection = get_connection(module) + if connection.get_device_info(): + os_version = connection.get_device_info()["network_os_major_version"] + return os_version diff --git a/plugins/modules/vyos_firewall_global.py b/plugins/modules/vyos_firewall_global.py index 205ef136..befe5e73 100644 --- a/plugins/modules/vyos_firewall_global.py +++ b/plugins/modules/vyos_firewall_global.py @@ -1,1223 +1,1236 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################# # WARNING # ############################################# # # This file is auto generated by the resource # module builder playbook. # # Do not edit this file manually. # # Changes to this file will be over written # by the resource module builder. # # Changes should be made in the model used to # generate this file or in the resource module # builder template. # ############################################# """ The module file for vyos_firewall_global """ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_firewall_global short_description: FIREWALL global resource module description: This module manage global policies or configurations for firewall on VyOS devices. version_added: 1.0.0 notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(ansible.netcommon.network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). author: - Rohit Thakur (@rohitthakur2590) options: config: description: - A dictionary of Firewall global configuration options. type: dict suboptions: route_redirects: description: -A dictionary of Firewall icmp redirect and source route global configuration options. type: list elements: dict suboptions: afi: description: - Specifies IP address type type: str choices: - ipv4 - ipv6 required: true icmp_redirects: description: - Specifies whether to allow sending/receiving of IPv4/v6 ICMP redirect messages. type: dict suboptions: send: description: - Permits or denies transmitting packets ICMP redirect messages. type: bool receive: description: - Permits or denies receiving packets ICMP redirect messages. type: bool ip_src_route: description: - Specifies whether or not to process source route IP options. type: bool ping: description: - Policy for handling of all IPv4 ICMP echo requests. type: dict suboptions: all: description: - Enables or disables response to all IPv4 ICMP Echo Request (ping) messages. - The system responds to IPv4 ICMP Echo Request messages. type: bool broadcast: description: - Enables or disables response to broadcast IPv4 ICMP Echo Request and Timestamp Request messages. - IPv4 ICMP Echo and Timestamp Request messages are not processed. type: bool config_trap: description: - SNMP trap generation on firewall configuration changes. type: bool validation: description: - Specifies a policy for source validation by reversed path, as defined in RFC 3704. - (disable) No source validation is performed. - (loose) Enable Loose Reverse Path Forwarding as defined in RFC3704. - (strict) Enable Strict Reverse Path Forwarding as defined in RFC3704. type: str choices: - strict - loose - disable group: description: - Defines a group of objects for referencing in firewall rules. type: dict suboptions: address_group: description: - Defines a group of IP addresses for referencing in firewall rules. type: list elements: dict suboptions: afi: description: - Specifies IP address type type: str default: ipv4 choices: - ipv4 - ipv6 required: false name: description: - Name of the firewall address group. type: str required: true description: description: - Allows you to specify a brief description for the address group. type: str members: description: - Address-group members. - IPv4 address to match. - IPv4 range to match. type: list elements: dict suboptions: address: description: IP address. type: str network_group: description: - Defines a group of networks for referencing in firewall rules. type: list elements: dict suboptions: afi: description: - Specifies network address type type: str default: ipv4 choices: - ipv4 - ipv6 required: false name: description: - Name of the firewall network group. type: str required: true description: description: - Allows you to specify a brief description for the network group. type: str members: description: - Adds an IPv4 network to the specified network group. - The format is ip-address/prefix. type: list elements: dict suboptions: address: description: IP address. type: str port_group: description: - Defines a group of ports for referencing in firewall rules. type: list elements: dict suboptions: name: description: - Name of the firewall port group. type: str required: true description: description: - Allows you to specify a brief description for the port group. type: str members: description: - Port-group member. type: list elements: dict suboptions: port: description: Defines the number. type: str log_martians: description: - Specifies whether or not to record packets with invalid addresses in the log. - (True) Logs packets with invalid addresses. - (False) Does not log packets with invalid addresses. type: bool syn_cookies: description: - Specifies policy for using TCP SYN cookies with IPv4. - (True) Enables TCP SYN cookies with IPv4. - (False) Disables TCP SYN cookies with IPv4. type: bool twa_hazards_protection: description: - RFC1337 TCP TIME-WAIT assassination hazards protection. type: bool state_policy: description: - Specifies global firewall state-policy. type: list elements: dict suboptions: connection_type: description: Specifies connection type. type: str choices: - established - invalid - related action: description: - Action for packets part of an established connection. type: str choices: - accept - drop - reject log: description: - Enable logging of packets part of an established connection. type: bool + log_level: + description: + - Only available in 1.4+ + type: str + choices: + - emerg + - alert + - crit + - err + - warn + - notice + - info + - debug running_config: description: - The module, by default, will connect to the remote device and retrieve the current running-config to use as a base for comparing against the contents of source. There are times when it is not desirable to have the task get the current running-config for every task in a playbook. The I(running_config) argument allows the implementer to pass in the configuration to use as the base config for comparison. This value of this option should be the output received from device by executing command C(show configuration commands | grep 'firewall') type: str state: description: - The state the configuration should be left in. type: str choices: - merged - replaced - deleted - gathered - rendered - parsed default: merged """ EXAMPLES = """ # Using merged # # Before state: # ------------- # # vyos@vyos# run show configuration commands | grep firewall # # - name: Merge the provided configuration with the existing running configuration vyos.vyos.vyos_firewall_global: config: validation: strict config_trap: true log_martians: true syn_cookies: true twa_hazards_protection: true ping: all: true broadcast: true state_policy: - connection_type: established action: accept log: true - connection_type: invalid action: reject route_redirects: - afi: ipv4 ip_src_route: true icmp_redirects: send: true receive: false group: address_group: - name: MGMT-HOSTS description: This group has the Management hosts address list members: - address: 192.0.1.1 - address: 192.0.1.3 - address: 192.0.1.5 network_group: - name: MGMT description: This group has the Management network addresses members: - address: 192.0.1.0/24 state: merged # # # ------------------------- # Module Execution Result # ------------------------- # # before": [] # # "commands": [ # "set firewall group address-group MGMT-HOSTS address 192.0.1.1", # "set firewall group address-group MGMT-HOSTS address 192.0.1.3", # "set firewall group address-group MGMT-HOSTS address 192.0.1.5", # "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list'", # "set firewall group address-group MGMT-HOSTS", # "set firewall group network-group MGMT network 192.0.1.0/24", # "set firewall group network-group MGMT description 'This group has the Management network addresses'", # "set firewall group network-group MGMT", # "set firewall ip-src-route 'enable'", # "set firewall receive-redirects 'disable'", # "set firewall send-redirects 'enable'", # "set firewall config-trap 'enable'", # "set firewall state-policy established action 'accept'", # "set firewall state-policy established log 'enable'", # "set firewall state-policy invalid action 'reject'", # "set firewall broadcast-ping 'enable'", # "set firewall all-ping 'enable'", # "set firewall log-martians 'enable'", # "set firewall twa-hazards-protection 'enable'", # "set firewall syn-cookies 'enable'", # "set firewall source-validation 'strict'" # ] # # "after": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "This group has the Management hosts address list", # "members": [ # { # "address": "192.0.1.1" # }, # { # "address": "192.0.1.3" # }, # { # "address": "192.0.1.5" # } # ], # "name": "MGMT-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # After state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group MGMT-HOSTS address '192.0.1.1' # set firewall group address-group MGMT-HOSTS address '192.0.1.3' # set firewall group address-group MGMT-HOSTS address '192.0.1.5' # set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # # # Using parsed # # - name: Render the commands for provided configuration vyos.vyos.vyos_firewall_global: running_config: "set firewall all-ping 'enable' set firewall broadcast-ping 'enable' set firewall config-trap 'enable' set firewall group address-group ENG-HOSTS address '192.0.3.1' set firewall group address-group ENG-HOSTS address '192.0.3.2' set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' set firewall group address-group SALES-HOSTS address '192.0.2.1' set firewall group address-group SALES-HOSTS address '192.0.2.2' set firewall group address-group SALES-HOSTS address '192.0.2.3' set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' set firewall group network-group MGMT description 'This group has the Management network addresses' set firewall group network-group MGMT network '192.0.1.0/24' set firewall ip-src-route 'enable' set firewall log-martians 'enable' set firewall receive-redirects 'disable' set firewall send-redirects 'enable' set firewall source-validation 'strict' set firewall state-policy established action 'accept' set firewall state-policy established log 'enable' set firewall state-policy invalid action 'reject' set firewall syn-cookies 'enable' set firewall twa-hazards-protection 'enable'" state: parsed # # # ------------------------- # Module Execution Result # ------------------------- # # # "parsed": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.3.1" # }, # { # "address": "192.0.3.2" # } # ], # "name": "ENG-HOSTS" # }, # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.2.1" # }, # { # "address": "192.0.2.2" # }, # { # "address": "192.0.2.3" # } # ], # "name": "SALES-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # } # # # Using deleted # # Before state # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group MGMT-HOSTS address '192.0.1.1' # set firewall group address-group MGMT-HOSTS address '192.0.1.3' # set firewall group address-group MGMT-HOSTS address '192.0.1.5' # set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' - name: Delete attributes of firewall. vyos.vyos.vyos_firewall_global: config: state_policy: config_trap: log_martians: syn_cookies: twa_hazards_protection: route_redirects: ping: group: state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "This group has the Management hosts address list", # "members": [ # { # "address": "192.0.1.1" # }, # { # "address": "192.0.1.3" # }, # { # "address": "192.0.1.5" # } # ], # "name": "MGMT-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # "commands": [ # "delete firewall source-validation", # "delete firewall group", # "delete firewall log-martians", # "delete firewall ip-src-route", # "delete firewall receive-redirects", # "delete firewall send-redirects", # "delete firewall config-trap", # "delete firewall state-policy", # "delete firewall syn-cookies", # "delete firewall broadcast-ping", # "delete firewall all-ping", # "delete firewall twa-hazards-protection" # ] # # "after": [] # # After state # ------------ # vyos@192# run show configuration commands | grep firewall # set 'firewall' # # # Using replaced # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group MGMT-HOSTS address '192.0.1.1' # set firewall group address-group MGMT-HOSTS address '192.0.1.3' # set firewall group address-group MGMT-HOSTS address '192.0.1.5' # set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' - name: Replace firewall global attributes configuration. vyos.vyos.vyos_firewall_global: config: validation: strict config_trap: true log_martians: true syn_cookies: true twa_hazards_protection: true ping: null all: true broadcast: true state_policy: - connection_type: established action: accept log: true - connection_type: invalid action: reject route_redirects: - afi: ipv4 ip_src_route: true icmp_redirects: send: true receive: false group: address_group: - name: SALES-HOSTS description: Sales office hosts address list members: - address: 192.0.2.1 - address: 192.0.2.2 - address: 192.0.2.3 - name: ENG-HOSTS description: Sales office hosts address list members: - address: 192.0.3.1 - address: 192.0.3.2 network_group: - name: MGMT description: This group has the Management network addresses members: - address: 192.0.1.0/24 state: replaced # # # ------------------------- # Module Execution Result # ------------------------- # # "before": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "This group has the Management hosts address list", # "members": [ # { # "address": "192.0.1.1" # }, # { # "address": "192.0.1.3" # }, # { # "address": "192.0.1.5" # } # ], # "name": "MGMT-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # "commands": [ # "delete firewall group address-group MGMT-HOSTS", # "set firewall group address-group SALES-HOSTS address 192.0.2.1", # "set firewall group address-group SALES-HOSTS address 192.0.2.2", # "set firewall group address-group SALES-HOSTS address 192.0.2.3", # "set firewall group address-group SALES-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group SALES-HOSTS", # "set firewall group address-group ENG-HOSTS address 192.0.3.1", # "set firewall group address-group ENG-HOSTS address 192.0.3.2", # "set firewall group address-group ENG-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group ENG-HOSTS" # ] # # "after": { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.3.1" # }, # { # "address": "192.0.3.2" # } # ], # "name": "ENG-HOSTS" # }, # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.2.1" # }, # { # "address": "192.0.2.2" # }, # { # "address": "192.0.2.3" # } # ], # "name": "SALES-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # After state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group ENG-HOSTS address '192.0.3.1' # set firewall group address-group ENG-HOSTS address '192.0.3.2' # set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' # set firewall group address-group SALES-HOSTS address '192.0.2.1' # set firewall group address-group SALES-HOSTS address '192.0.2.2' # set firewall group address-group SALES-HOSTS address '192.0.2.3' # set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # # # Using gathered # # Before state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group ENG-HOSTS address '192.0.3.1' # set firewall group address-group ENG-HOSTS address '192.0.3.2' # set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' # set firewall group address-group SALES-HOSTS address '192.0.2.1' # set firewall group address-group SALES-HOSTS address '192.0.2.2' # set firewall group address-group SALES-HOSTS address '192.0.2.3' # set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # - name: Gather firewall global config with provided configurations vyos.vyos.vyos_firewall_global: state: gathered # # # ------------------------- # Module Execution Result # ------------------------- # # "gathered": [ # { # "config_trap": true, # "group": { # "address_group": [ # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.3.1" # }, # { # "address": "192.0.3.2" # } # ], # "name": "ENG-HOSTS" # }, # { # "description": "Sales office hosts address list", # "members": [ # { # "address": "192.0.2.1" # }, # { # "address": "192.0.2.2" # }, # { # "address": "192.0.2.3" # } # ], # "name": "SALES-HOSTS" # } # ], # "network_group": [ # { # "description": "This group has the Management network addresses", # "members": [ # { # "address": "192.0.1.0/24" # } # ], # "name": "MGMT" # } # ] # }, # "log_martians": true, # "ping": { # "all": true, # "broadcast": true # }, # "route_redirects": [ # { # "afi": "ipv4", # "icmp_redirects": { # "receive": false, # "send": true # }, # "ip_src_route": true # } # ], # "state_policy": [ # { # "action": "accept", # "connection_type": "established", # "log": true # }, # { # "action": "reject", # "connection_type": "invalid" # } # ], # "syn_cookies": true, # "twa_hazards_protection": true, # "validation": "strict" # } # # After state: # ------------- # # vyos@192# run show configuration commands | grep firewall # set firewall all-ping 'enable' # set firewall broadcast-ping 'enable' # set firewall config-trap 'enable' # set firewall group address-group ENG-HOSTS address '192.0.3.1' # set firewall group address-group ENG-HOSTS address '192.0.3.2' # set firewall group address-group ENG-HOSTS description 'Sales office hosts address list' # set firewall group address-group SALES-HOSTS address '192.0.2.1' # set firewall group address-group SALES-HOSTS address '192.0.2.2' # set firewall group address-group SALES-HOSTS address '192.0.2.3' # set firewall group address-group SALES-HOSTS description 'Sales office hosts address list' # set firewall group network-group MGMT description 'This group has the Management network addresses' # set firewall group network-group MGMT network '192.0.1.0/24' # set firewall ip-src-route 'enable' # set firewall log-martians 'enable' # set firewall receive-redirects 'disable' # set firewall send-redirects 'enable' # set firewall source-validation 'strict' # set firewall state-policy established action 'accept' # set firewall state-policy established log 'enable' # set firewall state-policy invalid action 'reject' # set firewall syn-cookies 'enable' # set firewall twa-hazards-protection 'enable' # Using rendered # # - name: Render the commands for provided configuration vyos.vyos.vyos_firewall_global: config: validation: strict config_trap: true log_martians: true syn_cookies: true twa_hazards_protection: true ping: null all: true broadcast: true state_policy: - connection_type: established action: accept log: true - connection_type: invalid action: reject route_redirects: - afi: ipv4 ip_src_route: true icmp_redirects: null send: true receive: false group: address_group: - name: SALES-HOSTS description: Sales office hosts address list members: - address: 192.0.2.1 - address: 192.0.2.2 - address: 192.0.2.3 - name: ENG-HOSTS description: Sales office hosts address list members: - address: 192.0.3.1 - address: 192.0.3.2 network_group: - name: MGMT description: This group has the Management network addresses members: - address: 192.0.1.0/24 state: rendered # # # ------------------------- # Module Execution Result # ------------------------- # # # "rendered": [ # "set firewall group address-group SALES-HOSTS address 192.0.2.1", # "set firewall group address-group SALES-HOSTS address 192.0.2.2", # "set firewall group address-group SALES-HOSTS address 192.0.2.3", # "set firewall group address-group SALES-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group SALES-HOSTS", # "set firewall group address-group ENG-HOSTS address 192.0.3.1", # "set firewall group address-group ENG-HOSTS address 192.0.3.2", # "set firewall group address-group ENG-HOSTS description 'Sales office hosts address list'", # "set firewall group address-group ENG-HOSTS", # "set firewall group network-group MGMT network 192.0.1.0/24", # "set firewall group network-group MGMT description 'This group has the Management network addresses'", # "set firewall group network-group MGMT", # "set firewall ip-src-route 'enable'", # "set firewall receive-redirects 'disable'", # "set firewall send-redirects 'enable'", # "set firewall config-trap 'enable'", # "set firewall state-policy established action 'accept'", # "set firewall state-policy established log 'enable'", # "set firewall state-policy invalid action 'reject'", # "set firewall broadcast-ping 'enable'", # "set firewall all-ping 'enable'", # "set firewall log-martians 'enable'", # "set firewall twa-hazards-protection 'enable'", # "set firewall syn-cookies 'enable'", # "set firewall source-validation 'strict'" # ] # # """ RETURN = """ before: description: The configuration prior to the model invocation. returned: always type: list sample: > The configuration returned will always be in the same format of the parameters above. after: description: The resulting configuration model invocation. returned: when changed type: list sample: > The configuration returned will always be in the same format of the parameters above. commands: description: The set of commands pushed to the remote device. returned: always type: list sample: ['set firewall group address-group ENG-HOSTS', 'set firewall group address-group ENG-HOSTS address 192.0.3.1'] """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_global.firewall_global import ( Firewall_globalArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global import ( Firewall_global, ) def main(): """ Main entry point for module execution :returns: the result form module invocation """ required_if = [ ("state", "merged", ("config",)), ("state", "replaced", ("config",)), ("state", "parsed", ("running_config",)), ] mutually_exclusive = [("config", "running_config")] module = AnsibleModule( argument_spec=Firewall_globalArgs.argument_spec, required_if=required_if, supports_check_mode=True, mutually_exclusive=mutually_exclusive, ) result = Firewall_global(module).execute_module() module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_firewall_rules.py b/plugins/modules/vyos_firewall_rules.py index 06a300f5..fd2e7d55 100644 --- a/plugins/modules/vyos_firewall_rules.py +++ b/plugins/modules/vyos_firewall_rules.py @@ -1,1522 +1,1660 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################# # WARNING # ############################################# # # This file is auto generated by the resource # module builder playbook. # # Do not edit this file manually. # # Changes to this file will be over written # by the resource module builder. # # Changes should be made in the model used to # generate this file or in the resource module # builder template. # ############################################# """ The module file for vyos_firewall_rules """ from __future__ import absolute_import, division, print_function __metaclass__ = type +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} DOCUMENTATION = """ module: vyos_firewall_rules short_description: FIREWALL rules resource module description: This module manages firewall rule-set attributes on VyOS devices version_added: 1.0.0 notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(ansible.netcommon.network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). author: - Rohit Thakur (@rohitthakur2590) options: config: description: A dictionary of Firewall rule-set options. type: list elements: dict suboptions: afi: description: - Specifies the type of rule-set. type: str choices: - ipv4 - ipv6 required: true rule_sets: description: - The Firewall rule-set list. type: list elements: dict suboptions: + filter: + description: + - Filter type (exclusive to "name"). + - Supported in 1.4 and later. + type: str + choices: ['input', 'output', 'forward'] name: description: - Firewall rule set name. + - Required for 1.3- and optional for 1.4+. type: str default_action: description: - Default action for rule-set. - drop (Drop if no prior rules are hit (default)) - reject (Drop and notify source if no prior rules are hit) - accept (Accept if no prior rules are hit) + - jump (Jump to another rule-set, 1.4+) + type: str + choices: ['drop', 'reject', 'accept', 'jump'] + default_jump_target: + description: + - Default jump target if the default action is jump. + - Only valid in 1.4 and later. + - Only valid when default_action = jump. type: str - choices: - - drop - - reject - - accept description: description: - Rule set description. type: str enable_default_log: description: - Option to log packets hitting default-action. type: bool rules: description: - A dictionary that specifies the rule-set configurations. type: list elements: dict suboptions: number: description: - Rule number. type: int required: true description: description: - Description of this rule. type: str action: description: - Specifying the action. + - inspect is available < 1.4 + - continue, return, jump, queue, synproxy are available >= 1.4 type: str choices: - drop - reject - accept - inspect + - continue + - return + - jump + - queue + - synproxy destination: description: - Specifying the destination parameters. type: dict suboptions: address: description: - Destination ip address subnet or range. - IPv4/6 address, subnet or range to match. - Match everything except the specified address, subnet or range. - Destination ip address subnet or range. type: str group: description: - Destination group. type: dict suboptions: address_group: description: - Group of addresses. type: str network_group: description: - Group of networks. type: str port_group: description: - Group of ports. type: str port: description: - Multiple destination ports can be specified as a comma-separated list. - The whole list can also be "negated" using '!'. - For example:'!22,telnet,http,123,1001-1005'. type: str disable: description: - Option to disable firewall rule. + - aliased to disabled type: bool aliases: ["disabled"] fragment: description: - IP fragment match. type: str choices: - match-frag - match-non-frag icmp: description: - ICMP type and code information. type: dict suboptions: type_name: description: - ICMP type-name. type: str choices: - any - echo-reply - destination-unreachable - network-unreachable - host-unreachable - protocol-unreachable - port-unreachable - fragmentation-needed - source-route-failed - network-unknown - host-unknown - network-prohibited - host-prohibited - TOS-network-unreachable - TOS-host-unreachable - communication-prohibited - host-precedence-violation - precedence-cutoff - source-quench - redirect - network-redirect - host-redirect - TOS-network-redirect - TOS-host-redirect - echo-request - router-advertisement - router-solicitation - time-exceeded - ttl-zero-during-transit - ttl-zero-during-reassembly - parameter-problem - ip-header-bad - required-option-missing - timestamp-request - timestamp-reply - address-mask-request - address-mask-reply - ping - pong - ttl-exceeded code: description: - ICMP code. type: int type: description: - ICMP type. type: int + inbound_interface: + description: + - Inbound interface. + - Only valid in 1.4 and later. + type: dict + suboptions: + name: + description: + - Interface name. + - Can have wildcards + type: str + group: + description: + - Interface group. + type: str ipsec: description: - Inbound ip sec packets. type: str choices: - match-ipsec - match-none - log: + - match-ipsec-in + - match-ipsec-out + - match-none-in + - match-none-out + jump_target: description: - - Option to log packets matching rule + - Jump target if the action is jump. + - Only valid in 1.4 and later. + - Only valid when action = jump. type: str - choices: - - disable - - enable limit: description: - Rate limit using a token bucket filter. type: dict suboptions: burst: description: - Maximum number of packets to allow in excess of rate. type: int rate: description: - format for rate (integer/time unit). - any one of second, minute, hour or day may be used to specify time unit. - eg. 1/second implies rule to be matched at an average of once per second. type: dict suboptions: number: description: - This is the integer value. type: int unit: description: - This is the time unit. type: str + log: + description: + - Log matching packets. + type: str + choices: ['disable', 'enable'] + outbound_interface: + description: + - Match outbound interface. + - Only valid in 1.4 and later. + type: dict + suboptions: + name: + description: + - Interface name. + - Can have wildcards + type: str + group: + description: + - Interface group. + type: str + packet_length: + description: + - Packet length match. + - Only valid in 1.4 and later. + - Multiple values from 1 to 65535 and ranges are supported + type: list + elements: dict + suboptions: + length: + description: + - Packet length or range. + type: str + packet_length_exclude: + description: + - Packet length match. + - Only valid in 1.4 and later. + - Multiple values from 1 to 65535 and ranges are supported + type: list + elements: dict + suboptions: + length: + description: + - Packet length or range. + type: str + packet_type: + description: + - Packet type match. + type: str + choices: ['broadcast', 'multicast', 'host', 'other'] p2p: description: - P2P application packets. type: list elements: dict suboptions: application: description: - Name of the application. type: str choices: - all - applejuice - bittorrent - directconnect - edonkey - gnutella - kazaa protocol: description: - Protocol to match (protocol name in /etc/protocols or protocol number or all). - IP protocol name from /etc/protocols (e.g. "tcp" or "udp"). - <0-255> IP protocol number. - tcp_udp Both TCP and UDP. - all All IP protocols. - (!)All IP protocols except for the specified name or number. type: str + queue: + description: + - Queue options. + - Only valid in 1.4 and later. + - Only valid when action = queue. + - Can be a queue number or range. + type: str + queue_options: + description: + - Queue options. + - Only valid in 1.4 and later. + - Only valid when action = queue. + type: str + choices: ['bypass', 'fanout'] recent: description: - Parameters for matching recently seen sources. type: dict suboptions: count: description: - Source addresses seen more than N times. type: int time: description: - Source addresses seen in the last N seconds. - type: int + - Since 1.4, this is a string of second/minute/hour + type: str source: description: - Source parameters. type: dict suboptions: address: description: - Source ip address subnet or range. - IPv4/6 address, subnet or range to match. - Match everything except the specified address, subnet or range. - Source ip address subnet or range. type: str group: description: - Source group. type: dict suboptions: address_group: description: - Group of addresses. type: str network_group: description: - Group of networks. type: str port_group: description: - Group of ports. type: str port: description: - Multiple source ports can be specified as a comma-separated list. - The whole list can also be "negated" using '!'. - For example:'!22,telnet,http,123,1001-1005'. type: str mac_address: description: - MAC address to match. - Match everything except the specified MAC address. type: str + fqdn: + description: + - Fully qualified domain name. + - Available in 1.4 and later. + type: str + state: description: - Session state. type: dict suboptions: established: description: - Established state. type: bool invalid: description: - Invalid state. type: bool new: description: - New state. type: bool related: description: - Related state. type: bool + synproxy: + description: + - SYN proxy options. + - Only valid in 1.4 and later. + - Only valid when action = synproxy. + type: dict + suboptions: + mss: + description: + - Adjust MSS (501-65535) + type: int + window_scale: + description: + - Window scale (1-14). + type: int tcp: description: - TCP flags to match. type: dict suboptions: flags: description: - - TCP flags to be matched. - type: str + - list of tcp flags to be matched + - 5.0 breaking change to support 1.4+ and 1.3- + type: list + elements: dict + suboptions: + flag: + description: + - TCP flag to be matched. + - syn, ack, fin, rst, urg, psh, all (1.3-) + - syn, ack, fin, rst, urg, psh, cwr, ecn (1.4+) + type: str + choices: ['ack', 'cwr', 'ecn', 'fin', 'psh', 'rst', 'syn', 'urg', 'all'] + invert: + description: + - Invert the match. + type: bool time: description: - Time to match rule. type: dict suboptions: utc: description: - Interpret times for startdate, stopdate, starttime and stoptime to be UTC. type: bool monthdays: description: - Monthdays to match rule on. type: str startdate: description: - Date to start matching rule. type: str starttime: description: - Time of day to start matching rule. type: str stopdate: description: - Date to stop matching rule. type: str stoptime: description: - Time of day to stop matching rule. type: str weekdays: description: - Weekdays to match rule on. type: str running_config: description: - This option is used only with state I(parsed). - The value of this option should be the output received from the VyOS device by executing the command B(show configuration commands | grep firewall). - The state I(parsed) reads the configuration from C(running_config) option and transforms it into Ansible structured data as per the resource module's argspec and the value is then returned in the I(parsed) key within the result. type: str state: description: - The state the configuration should be left in type: str choices: - merged - replaced - overridden - deleted - gathered - rendered - parsed default: merged """ EXAMPLES = """ # Using deleted to delete firewall rules based on rule-set name # # Before state # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' - name: Delete attributes of given firewall rules. vyos.vyos.vyos_firewall_rules: config: - afi: ipv4 rule_sets: - name: Downlink state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # "commands": [ # "delete firewall name Downlink" # ] # # "after": [] # After state # ------------ # vyos@vyos# run show configuration commands | grep firewall # set firewall group address-group 'inbound' # Using deleted to delete firewall rules based on afi # # Before state # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' - name: Delete attributes of given firewall rules. vyos.vyos.vyos_firewall_rules: config: - afi: ipv4 state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # "commands": [ # "delete firewall name" # ] # # "after": [] # After state # ------------ # vyos@vyos:~$ show configuration commands| grep firewall # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # Using deleted to delete all the the firewall rules when provided config is empty # # Before state # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' # - name: Delete attributes of given firewall rules. vyos.vyos.vyos_firewall_rules: state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # "commands": [ # "delete firewall name" # ] # # "after": [] # After state # ------------ # vyos@vyos# run show configuration commands | grep firewall # set firewall group address-group 'inbound' # Using merged # # Before state: # ------------- # # vyos@vyos# run show configuration commands | grep firewall # set firewall group address-group 'inbound' # - name: Merge the provided configuration with the existing running configuration vyos.vyos.vyos_firewall_rules: config: - afi: ipv6 rule_sets: - name: UPLINK description: This is ipv6 specific rule-set default_action: accept rules: - number: 1 action: accept description: Fwipv6-Rule 1 is configured by Ansible ipsec: match-ipsec - number: 2 action: accept description: Fwipv6-Rule 2 is configured by Ansible ipsec: match-ipsec - afi: ipv4 rule_sets: - name: INBOUND description: IPv4 INBOUND rule set default_action: accept rules: - number: 101 action: accept description: Rule 101 is configured by Ansible ipsec: match-ipsec - number: 102 action: reject description: Rule 102 is configured by Ansible ipsec: match-ipsec - number: 103 action: accept description: Rule 103 is configured by Ansible destination: group: address_group: inbound source: address: 192.0.2.0 state: established: true new: false invalid: false related: true state: merged # # # ------------------------- # Module Execution Result # ------------------------- # # before": [] # # "commands": [ # "set firewall ipv6-name UPLINK default-action 'accept'", # "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", # "set firewall ipv6-name UPLINK rule 1 action 'accept'", # "set firewall ipv6-name UPLINK rule 1", # "set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible'", # "set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec'", # "set firewall ipv6-name UPLINK rule 2 action 'accept'", # "set firewall ipv6-name UPLINK rule 2", # "set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible'", # "set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec'", # "set firewall name INBOUND default-action 'accept'", # "set firewall name INBOUND description 'IPv4 INBOUND rule set'", # "set firewall name INBOUND rule 101 action 'accept'", # "set firewall name INBOUND rule 101", # "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", # "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 102 action 'reject'", # "set firewall name INBOUND rule 102", # "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", # "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", # "set firewall name INBOUND rule 103 destination group address-group inbound", # "set firewall name INBOUND rule 103", # "set firewall name INBOUND rule 103 source address 192.0.2.0", # "set firewall name INBOUND rule 103 state established enable", # "set firewall name INBOUND rule 103 state related enable", # "set firewall name INBOUND rule 103 state invalid disable", # "set firewall name INBOUND rule 103 state new disable", # "set firewall name INBOUND rule 103 action 'accept'" # ] # # "after": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 102 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 102 # }, # { # "action": "accept", # "description": "Rule 103 is configured by Ansible", # "destination": { # "group": { # "address_group": "inbound" # } # }, # "number": 103, # "source": { # "address": "192.0.2.0" # }, # "state": { # "established": true, # "invalid": false, # "new": false, # "related": true # } # } # ] # } # ] # } # ] # # After state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # Using replaced # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # - name: >- Replace device configurations of listed firewall rules with provided configurations vyos.vyos.vyos_firewall_rules: config: - afi: ipv6 rule_sets: - name: UPLINK description: This is ipv6 specific rule-set default_action: accept - afi: ipv4 rule_sets: - name: INBOUND description: IPv4 INBOUND rule set default_action: accept rules: - number: 101 action: accept description: Rule 101 is configured by Ansible ipsec: match-ipsec - number: 104 action: reject description: Rule 104 is configured by Ansible ipsec: match-none state: replaced # # # ------------------------- # Module Execution Result # ------------------------- # # "before": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 102 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 102 # }, # { # "action": "accept", # "description": "Rule 103 is configured by Ansible", # "destination": { # "group": { # "address_group": "inbound" # } # }, # "number": 103, # "source": { # "address": "192.0.2.0" # }, # "state": { # "established": true, # "invalid": false, # "new": false, # "related": true # } # } # ] # } # ] # } # ] # # "commands": [ # "delete firewall ipv6-name UPLINK rule 1", # "delete firewall ipv6-name UPLINK rule 2", # "delete firewall name INBOUND rule 102", # "delete firewall name INBOUND rule 103", # "set firewall name INBOUND rule 104 action 'reject'", # "set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible'", # "set firewall name INBOUND rule 104", # "set firewall name INBOUND rule 104 ipsec 'match-none'" # ] # # "after": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK" # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 104 is configured by Ansible", # "ipsec": "match-none", # "number": 104 # } # ] # } # ] # } # ] # # After state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 104 action 'reject' # set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' # set firewall name INBOUND rule 104 ipsec 'match-none' # Using overridden # # Before state # -------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 104 action 'reject' # set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' # set firewall name INBOUND rule 104 ipsec 'match-none' # - name: Overrides all device configuration with provided configuration vyos.vyos.vyos_firewall_rules: config: - afi: ipv4 rule_sets: - name: Downlink description: IPv4 INBOUND rule set default_action: accept rules: - number: 501 action: accept description: Rule 501 is configured by Ansible ipsec: match-ipsec - number: 502 action: reject description: Rule 502 is configured by Ansible ipsec: match-ipsec state: overridden # # # ------------------------- # Module Execution Result # ------------------------- # # "before": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK" # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 104 is configured by Ansible", # "ipsec": "match-none", # "number": 104 # } # ] # } # ] # } # ] # # "commands": [ # "delete firewall ipv6-name UPLINK", # "delete firewall name INBOUND", # "set firewall name Downlink default-action 'accept'", # "set firewall name Downlink description 'IPv4 INBOUND rule set'", # "set firewall name Downlink rule 501 action 'accept'", # "set firewall name Downlink rule 501", # "set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible'", # "set firewall name Downlink rule 501 ipsec 'match-ipsec'", # "set firewall name Downlink rule 502 action 'reject'", # "set firewall name Downlink rule 502", # "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'", # "set firewall name Downlink rule 502 ipsec 'match-ipsec'" # # # "after": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] # # # After state # ------------ # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall name Downlink default-action 'accept' # set firewall name Downlink description 'IPv4 INBOUND rule set' # set firewall name Downlink rule 501 action 'accept' # set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' # set firewall name Downlink rule 501 ipsec 'match-ipsec' # set firewall name Downlink rule 502 action 'reject' # set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' # set firewall name Downlink rule 502 ipsec 'match-ipsec' # Using gathered # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # - name: Gather listed firewall rules with provided configurations vyos.vyos.vyos_firewall_rules: state: gathered # # # ------------------------- # Module Execution Result # ------------------------- # # "gathered": [ # { # "afi": "ipv6", # "rule_sets": [ # { # "default_action": "accept", # "description": "This is ipv6 specific rule-set", # "name": "UPLINK", # "rules": [ # { # "action": "accept", # "description": "Fwipv6-Rule 1 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 1 # }, # { # "action": "accept", # "description": "Fwipv6-Rule 2 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 2 # } # ] # } # ] # }, # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "INBOUND", # "rules": [ # { # "action": "accept", # "description": "Rule 101 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 101 # }, # { # "action": "reject", # "description": "Rule 102 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 102 # }, # { # "action": "accept", # "description": "Rule 103 is configured by Ansible", # "destination": { # "group": { # "address_group": "inbound" # } # }, # "number": 103, # "source": { # "address": "192.0.2.0" # }, # "state": { # "established": true, # "invalid": false, # "new": false, # "related": true # } # } # ] # } # ] # } # ] # # # After state: # ------------- # # vyos@vyos:~$ show configuration commands| grep firewall # set firewall group address-group 'inbound' # set firewall ipv6-name UPLINK default-action 'accept' # set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' # set firewall ipv6-name UPLINK rule 1 action 'accept' # set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' # set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' # set firewall ipv6-name UPLINK rule 2 action 'accept' # set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' # set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' # set firewall name INBOUND default-action 'accept' # set firewall name INBOUND description 'IPv4 INBOUND rule set' # set firewall name INBOUND rule 101 action 'accept' # set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' # set firewall name INBOUND rule 101 ipsec 'match-ipsec' # set firewall name INBOUND rule 102 action 'reject' # set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' # set firewall name INBOUND rule 102 ipsec 'match-ipsec' # set firewall name INBOUND rule 103 action 'accept' # set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' # set firewall name INBOUND rule 103 destination group address-group 'inbound' # set firewall name INBOUND rule 103 source address '192.0.2.0' # set firewall name INBOUND rule 103 state established 'enable' # set firewall name INBOUND rule 103 state invalid 'disable' # set firewall name INBOUND rule 103 state new 'disable' # set firewall name INBOUND rule 103 state related 'enable' # Using rendered # # - name: Render the commands for provided configuration vyos.vyos.vyos_firewall_rules: config: - afi: ipv6 rule_sets: - name: UPLINK description: This is ipv6 specific rule-set default_action: accept - afi: ipv4 rule_sets: - name: INBOUND description: IPv4 INBOUND rule set default_action: accept rules: - number: 101 action: accept description: Rule 101 is configured by Ansible ipsec: match-ipsec - number: 102 action: reject description: Rule 102 is configured by Ansible ipsec: match-ipsec - number: 103 action: accept description: Rule 103 is configured by Ansible destination: group: address_group: inbound source: address: 192.0.2.0 state: established: true new: false invalid: false related: true state: rendered # # # ------------------------- # Module Execution Result # ------------------------- # # # "rendered": [ # "set firewall ipv6-name UPLINK default-action 'accept'", # "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", # "set firewall name INBOUND default-action 'accept'", # "set firewall name INBOUND description 'IPv4 INBOUND rule set'", # "set firewall name INBOUND rule 101 action 'accept'", # "set firewall name INBOUND rule 101", # "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", # "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 102 action 'reject'", # "set firewall name INBOUND rule 102", # "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", # "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", # "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", # "set firewall name INBOUND rule 103 destination group address-group inbound", # "set firewall name INBOUND rule 103", # "set firewall name INBOUND rule 103 source address 192.0.2.0", # "set firewall name INBOUND rule 103 state established enable", # "set firewall name INBOUND rule 103 state related enable", # "set firewall name INBOUND rule 103 state invalid disable", # "set firewall name INBOUND rule 103 state new disable", # "set firewall name INBOUND rule 103 action 'accept'" # ] # Using parsed # # - name: Parsed the provided input commands. vyos.vyos.vyos_firewall_rules: running_config: "set firewall group address-group 'inbound' set firewall name Downlink default-action 'accept' set firewall name Downlink description 'IPv4 INBOUND rule set' set firewall name Downlink rule 501 action 'accept' set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' set firewall name Downlink rule 501 ipsec 'match-ipsec' set firewall name Downlink rule 502 action 'reject' set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' set firewall name Downlink rule 502 ipsec 'match-ipsec'" state: parsed # # # ------------------------- # Module Execution Result # ------------------------- # # # "parsed": [ # { # "afi": "ipv4", # "rule_sets": [ # { # "default_action": "accept", # "description": "IPv4 INBOUND rule set", # "name": "Downlink", # "rules": [ # { # "action": "accept", # "description": "Rule 501 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 501 # }, # { # "action": "reject", # "description": "Rule 502 is configured by Ansible", # "ipsec": "match-ipsec", # "number": 502 # } # ] # } # ] # } # ] """ RETURN = """ before: description: The configuration prior to the model invocation. returned: always - type: list + type: dict sample: > The configuration returned will always be in the same format of the parameters above. after: description: The resulting configuration model invocation. returned: when changed - type: list + type: dict sample: > The configuration returned will always be in the same format of the parameters above. commands: description: The set of commands pushed to the remote device. returned: always type: list sample: - "set firewall name Downlink default-action 'accept'" - "set firewall name Downlink description 'IPv4 INBOUND rule set'" - "set firewall name Downlink rule 501 action 'accept'" - "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'" - "set firewall name Downlink rule 502 ipsec 'match-ipsec'" """ from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import ( - Firewall_rulesArgs, -) -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules import ( - Firewall_rules, -) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import Firewall_rulesArgs +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules import Firewall_rules def main(): """ Main entry point for module execution + :returns: the result form module invocation """ required_if = [ ("state", "merged", ("config",)), ("state", "replaced", ("config",)), ("state", "rendered", ("config",)), ("state", "overridden", ("config",)), ("state", "parsed", ("running_config",)), ] mutually_exclusive = [("config", "running_config")] module = AnsibleModule( argument_spec=Firewall_rulesArgs.argument_spec, required_if=required_if, supports_check_mode=True, mutually_exclusive=mutually_exclusive, ) result = Firewall_rules(module).execute_module() module.exit_json(**result) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/plugins/modules/vyos_ospf_interfaces.py b/plugins/modules/vyos_ospf_interfaces.py index c2326895..33290581 100644 --- a/plugins/modules/vyos_ospf_interfaces.py +++ b/plugins/modules/vyos_ospf_interfaces.py @@ -1,912 +1,912 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2020 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################# # WARNING # ############################################# # # This file is auto generated by the resource # module builder playbook. # # Do not edit this file manually. # # Changes to this file will be over written # by the resource module builder. # # Changes should be made in the model used to # generate this file or in the resource module # builder template. # ############################################# """ The module file for vyos_ospf_interfaces """ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_ospf_interfaces version_added: 1.2.0 short_description: OSPF Interfaces Resource Module. description: - This module manages OSPF configuration of interfaces on devices running VYOS. author: Gomathi Selvi Srinivasan (@GomathiselviS) options: config: description: A list of OSPF configuration for interfaces. type: list elements: dict suboptions: name: description: - Name/Identifier of the interface. type: str address_family: description: - OSPF settings on the interfaces in address-family context. type: list elements: dict suboptions: afi: description: - Address Family Identifier (AFI) for OSPF settings on the interfaces. type: str choices: ['ipv4', 'ipv6'] required: true authentication: description: - Authentication settings on the interface. type: dict suboptions: plaintext_password: description: - Plain Text password. type: str md5_key: description: - md5 parameters. type: dict suboptions: key_id: description: - key id. type: int key: description: - md5 key. type: str bandwidth: description: - Bandwidth of interface (kilobits/sec) type: int cost: description: - metric associated with interface. type: int dead_interval: description: - Time interval to detect a dead router. type: int hello_interval: description: - Timer interval between transmission of hello packets. type: int mtu_ignore: description: - if True, Disable MTU check for Database Description packets. type: bool network: description: - Interface type. type: str priority: description: - Interface priority. type: int retransmit_interval: description: - LSA retransmission interval. type: int transmit_delay: description: - LSA transmission delay. type: int ifmtu: description: - interface MTU. type: int instance: description: - Instance ID. type: str passive: description: - If True, disables forming adjacency. type: bool running_config: description: - This option is used only with state I(parsed). - The value of this option should be the output received from the VYOS device by executing the command B(show configuration commands | match "set interfaces"). - The state I(parsed) reads the configuration from C(running_config) option and transforms it into Ansible structured data as per the resource module's argspec and the value is then returned in the I(parsed) key within the result. type: str state: description: - The state the configuration should be left in. type: str choices: - merged - replaced - overridden - deleted - gathered - parsed - rendered default: merged """ EXAMPLES = """ # Using merged # # Before state: # ------------- # # @vyos:~$ show configuration commands | match "ospf" - name: Merge provided configuration with device configuration vyos.vyos.vyos_ospf_interfaces: config: - name: "eth1" address_family: - afi: "ipv4" transmit_delay: 50 priority: 26 network: "point-to-point" - afi: "ipv6" dead_interval: 39 - name: "bond2" address_family: - afi: "ipv4" transmit_delay: 45 bandwidth: 70 authentication: md5_key: key_id: 10 key: "1111111111232345" - afi: "ipv6" passive: true state: merged # After State: # -------------- # vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345' # set interfaces bonding bond2 ip ospf bandwidth '70' # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth1 ip ospf network 'point-to-point' # set interfaces ethernet eth1 ip ospf priority '26' # set interfaces ethernet eth1 ip ospf transmit-delay '50' # set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39' # "after": [ # " # "address_family": [ # { # "afi": "ipv4", # "authentication": { # "md5_key": { # "key": "1111111111232345", # "key_id": 10 # } # }, # "bandwidth": 70, # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "network": "point-to-point", # "priority": 26, # "transmit_delay": 50 # }, # { # "afi": "ipv6", # "dead_interval": 39 # } # ], # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "before": [ # { # "name": "eth0" # }, # { # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "changed": true, # "commands": [ # "set interfaces ethernet eth1 ip ospf transmit-delay 50", # "set interfaces ethernet eth1 ip ospf priority 26", # "set interfaces ethernet eth1 ip ospf network point-to-point", # "set interfaces ethernet eth1 ipv6 ospfv3 dead-interval 39", # "set interfaces bonding bond2 ip ospf transmit-delay 45", # "set interfaces bonding bond2 ip ospf bandwidth 70", # "set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key 1111111111232345", # "set interfaces bonding bond2 ipv6 ospfv3 passive" # ], # Using replaced: # Before State: # ------------ # vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345' # set interfaces bonding bond2 ip ospf bandwidth '70' # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth1 ip ospf network 'point-to-point' # set interfaces ethernet eth1 ip ospf priority '26' # set interfaces ethernet eth1 ip ospf transmit-delay '50' # set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39' - name: Replace provided configuration with device configuration vyos.vyos.vyos_ospf_interfaces: config: - name: "eth1" address_family: - afi: "ipv4" cost: 100 - afi: "ipv6" ifmtu: 33 - name: "bond2" address_family: - afi: "ipv4" transmit_delay: 45 - afi: "ipv6" passive: true state: replaced # After State: # ----------- # vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth1 ip ospf cost '100' # set interfaces ethernet eth1 ipv6 ospfv3 ifmtu '33' # vyos@vyos:~$ # Module Execution # ---------------- # "after": [ # { # "address_family": [ # { # "afi": "ipv4", # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "cost": 100 # }, # { # "afi": "ipv6", # "ifmtu": 33 # } # ], # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "before": [ # { # "address_family": [ # { # "afi": "ipv4", # "authentication": { # "md5_key": { # "key": "1111111111232345", # "key_id": 10 # } # }, # "bandwidth": 70, # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "network": "point-to-point", # "priority": 26, # "transmit_delay": 50 # }, # { # "afi": "ipv6", # "dead_interval": 39 # } # ], # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "changed": true, # "commands": [ # "set interfaces ethernet eth1 ip ospf cost 100", # "set interfaces ethernet eth1 ipv6 ospfv3 ifmtu 33", # "delete interfaces ethernet eth1 ip ospf network point-to-point", # "delete interfaces ethernet eth1 ip ospf priority 26", # "delete interfaces ethernet eth1 ip ospf transmit-delay 50", # "delete interfaces ethernet eth1 ipv6 ospfv3 dead-interval 39", # "delete interfaces bonding bond2 ip ospf authentication", # "delete interfaces bonding bond2 ip ospf bandwidth 70" # ], # # Using Overridden: # ----------------- # Before State: # ------------ # vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345' # set interfaces bonding bond2 ip ospf bandwidth '70' # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth1 ip ospf cost '100' # set interfaces ethernet eth1 ip ospf network 'point-to-point' # set interfaces ethernet eth1 ip ospf priority '26' # set interfaces ethernet eth1 ip ospf transmit-delay '50' # set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39' # set interfaces ethernet eth1 ipv6 ospfv3 ifmtu '33' # vyos@vyos:~$ - name: Override device configuration with provided configuration vyos.vyos.vyos_ospf_interfaces: config: - name: "eth0" address_family: - afi: "ipv4" cost: 100 - afi: "ipv6" ifmtu: 33 passive: true state: overridden # After State: # ----------- # 200~vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces ethernet eth0 ip ospf cost '100' # set interfaces ethernet eth0 ipv6 ospfv3 ifmtu '33' # set interfaces ethernet eth0 ipv6 ospfv3 'passive' # vyos@vyos:~$ # # # "after": [ # { # "name": "bond2" # }, # { # "address_family": [ # { # "afi": "ipv4", # "cost": 100 # }, # { # "afi": "ipv6", # "ifmtu": 33, # "passive": true # } # ], # "name": "eth0" # }, # { # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "before": [ # { # "address_family": [ # { # "afi": "ipv4", # "authentication": { # "md5_key": { # "key": "1111111111232345", # "key_id": 10 # } # }, # "bandwidth": 70, # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "cost": 100, # "network": "point-to-point", # "priority": 26, # "transmit_delay": 50 # }, # { # "afi": "ipv6", # "dead_interval": 39, # "ifmtu": 33 # } # ], # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "changed": true, # "commands": [ # "delete interfaces bonding bond2 ip ospf", # "delete interfaces bonding bond2 ipv6 ospfv3", # "delete interfaces ethernet eth1 ip ospf", # "delete interfaces ethernet eth1 ipv6 ospfv3", # "set interfaces ethernet eth0 ip ospf cost 100", # "set interfaces ethernet eth0 ipv6 ospfv3 ifmtu 33", # "set interfaces ethernet eth0 ipv6 ospfv3 passive" # ], # # Using deleted: # ------------- # before state: # ------------- # vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345' # set interfaces bonding bond2 ip ospf bandwidth '70' # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth0 ip ospf cost '100' # set interfaces ethernet eth0 ipv6 ospfv3 ifmtu '33' # set interfaces ethernet eth0 ipv6 ospfv3 'passive' # set interfaces ethernet eth1 ip ospf network 'point-to-point' # set interfaces ethernet eth1 ip ospf priority '26' # set interfaces ethernet eth1 ip ospf transmit-delay '50' # set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39' # vyos@vyos:~$ - name: Delete device configuration vyos.vyos.vyos_ospf_interfaces: config: - name: "eth0" state: deleted # After State: # ----------- # vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345' # set interfaces bonding bond2 ip ospf bandwidth '70' # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth1 ip ospf network 'point-to-point' # set interfaces ethernet eth1 ip ospf priority '26' # set interfaces ethernet eth1 ip ospf transmit-delay '50' # set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39' # vyos@vyos:~$ # # # "after": [ # { # "address_family": [ # { # "afi": "ipv4", # "authentication": { # "md5_key": { # "key": "1111111111232345", # "key_id": 10 # } # }, # "bandwidth": 70, # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "network": "point-to-point", # "priority": 26, # "transmit_delay": 50 # }, # { # "afi": "ipv6", # "dead_interval": 39 # } # ], # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "before": [ # { # "address_family": [ # { # "afi": "ipv4", # "authentication": { # "md5_key": { # "key": "1111111111232345", # "key_id": 10 # } # }, # "bandwidth": 70, # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "address_family": [ # { # "afi": "ipv4", # "cost": 100 # }, # { # "afi": "ipv6", # "ifmtu": 33, # "passive": true # } # ], # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "network": "point-to-point", # "priority": 26, # "transmit_delay": 50 # }, # { # "afi": "ipv6", # "dead_interval": 39 # } # ], # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], # "changed": true, # "commands": [ # "delete interfaces ethernet eth0 ip ospf", # "delete interfaces ethernet eth0 ipv6 ospfv3" # ], # # Using parsed: # parsed.cfg: # set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345' # set interfaces bonding bond2 ip ospf bandwidth '70' # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth0 ip ospf cost '50' # set interfaces ethernet eth0 ip ospf priority '26' # set interfaces ethernet eth0 ipv6 ospfv3 instance-id '33' # set interfaces ethernet eth0 ipv6 ospfv3 'mtu-ignore' # set interfaces ethernet eth1 ip ospf network 'point-to-point' # set interfaces ethernet eth1 ip ospf priority '26' # set interfaces ethernet eth1 ip ospf transmit-delay '50' # set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39' # - name: parse configs vyos.vyos.vyos_ospf_interfaces: running_config: "{{ lookup('file', './parsed.cfg') }}" state: parsed # Module Execution: # ---------------- # "parsed": [ # { # "address_family": [ # { # "afi": "ipv4", # "authentication": { # "md5_key": { # "key": "1111111111232345", # "key_id": 10 # } # }, # "bandwidth": 70, # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "address_family": [ # { # "afi": "ipv4", # "cost": 50, # "priority": 26 # }, # { # "afi": "ipv6", # "instance": "33", # "mtu_ignore": true # } # ], # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "network": "point-to-point", # "priority": 26, # "transmit_delay": 50 # }, # { # "afi": "ipv6", # "dead_interval": 39 # } # ], # "name": "eth1" # } # ] # Using rendered: # -------------- - name: Render vyos.vyos.vyos_ospf_interfaces: config: - name: "eth1" address_family: - afi: "ipv4" transmit_delay: 50 priority: 26 network: "point-to-point" - afi: "ipv6" dead_interval: 39 - name: "bond2" address_family: - afi: "ipv4" transmit_delay: 45 bandwidth: 70 authentication: md5_key: key_id: 10 key: "1111111111232345" - afi: "ipv6" passive: true state: rendered # Module Execution: # ---------------- # "rendered": [ # "set interfaces ethernet eth1 ip ospf transmit-delay 50", # "set interfaces ethernet eth1 ip ospf priority 26", # "set interfaces ethernet eth1 ip ospf network point-to-point", # "set interfaces ethernet eth1 ipv6 ospfv3 dead-interval 39", # "set interfaces bonding bond2 ip ospf transmit-delay 45", # "set interfaces bonding bond2 ip ospf bandwidth 70", # "set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key 1111111111232345", # "set interfaces bonding bond2 ipv6 ospfv3 passive" # ] # # Using Gathered: # -------------- # Native Config: # vyos@vyos:~$ show configuration commands | match "ospf" # set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345' # set interfaces bonding bond2 ip ospf bandwidth '70' # set interfaces bonding bond2 ip ospf transmit-delay '45' # set interfaces bonding bond2 ipv6 ospfv3 'passive' # set interfaces ethernet eth1 ip ospf network 'point-to-point' # set interfaces ethernet eth1 ip ospf priority '26' # set interfaces ethernet eth1 ip ospf transmit-delay '50' # set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39' # vyos@vyos:~$ - name: gather configs vyos.vyos.vyos_ospf_interfaces: state: gathered # Module Execution: # ----------------- # "gathered": [ # { # "address_family": [ # { # "afi": "ipv4", # "authentication": { # "md5_key": { # "key": "1111111111232345", # "key_id": 10 # } # }, # "bandwidth": 70, # "transmit_delay": 45 # }, # { # "afi": "ipv6", # "passive": true # } # ], # "name": "bond2" # }, # { # "name": "eth0" # }, # { # "address_family": [ # { # "afi": "ipv4", # "network": "point-to-point", # "priority": 26, # "transmit_delay": 50 # }, # { # "afi": "ipv6", # "dead_interval": 39 # } # ], # "name": "eth1" # }, # { # "name": "eth2" # }, # { # "name": "eth3" # } # ], """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospf_interfaces.ospf_interfaces import ( Ospf_interfacesArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces import ( Ospf_interfaces, ) def main(): """ Main entry point for module execution :returns: the result form module invocation """ module = AnsibleModule( argument_spec=Ospf_interfacesArgs.argument_spec, mutually_exclusive=[], required_if=[], - supports_check_mode=False, + supports_check_mode=True, ) result = Ospf_interfaces(module).execute_module() module.exit_json(**result) if __name__ == "__main__": main() diff --git a/requirements.txt b/requirements.txt index e4c0f753..ee91c107 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ -ansible-pylibssh paramiko scp diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt new file mode 100644 index 00000000..c835eef8 --- /dev/null +++ b/tests/sanity/ignore-2.19.txt @@ -0,0 +1 @@ +plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg new file mode 100644 index 00000000..7b281de6 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg @@ -0,0 +1,16 @@ +set firewall group address-group RND-HOSTS address 192.0.2.1 +set firewall group address-group RND-HOSTS address 192.0.2.3 +set firewall group address-group RND-HOSTS address 192.0.2.5 +set firewall group address-group RND-HOSTS description 'This group has the Management hosts address lists' +set firewall group ipv6-address-group LOCAL-v6 address ::1 +set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1 +set firewall group ipv6-address-group LOCAL-v6 description 'This group has the hosts address lists of this machine' +set firewall group network-group RND network 192.0.2.0/24 +set firewall group network-group RND description 'This group has the Management network addresses' +set firewall group ipv6-network-group UNIQUE-LOCAL-v6 network fc00::/7 +set firewall group ipv6-network-group UNIQUE-LOCAL-v6 description 'This group encompasses the ULA address space in IPv6' +set firewall group port-group SSH port 22 +set firewall group port-group SSH description 'This group has the ssh ports' +set firewall global-options all-ping enable +set firewall global-options state-policy related action 'accept' +set firewall global-options state-policy related log-level 'alert' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg index a3aec78f..f1fdf1ea 100644 --- a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg @@ -1,15 +1,16 @@ set firewall name V4-INGRESS default-action 'accept' set firewall ipv6-name V6-INGRESS default-action 'accept' set firewall name V4-INGRESS description 'This is IPv4 V4-INGRESS rule set' set firewall name V4-INGRESS enable-default-log set firewall name V4-INGRESS rule 101 protocol 'icmp' set firewall name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible' set firewall name V4-INGRESS rule 101 fragment 'match-frag' set firewall name V4-INGRESS rule 101 set firewall name V4-INGRESS rule 101 'disable' set firewall name V4-INGRESS rule 101 action 'accept' set firewall name V4-INGRESS rule 101 ipsec 'match-ipsec' +set firewall name V4-INGRESS rule 101 log 'enable' set firewall name EGRESS default-action 'reject' set firewall ipv6-name EGRESS default-action 'reject' set firewall ipv6-name EGRESS rule 20 set firewall ipv6-name EGRESS rule 20 icmpv6 type 'echo-request' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg new file mode 100644 index 00000000..ef596cde --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg @@ -0,0 +1,26 @@ +set firewall ipv4 name V4-INGRESS default-action 'accept' +set firewall ipv6 name V6-INGRESS default-action 'accept' +set firewall ipv4 name V4-INGRESS description 'This is IPv4 V4-INGRESS rule set' +set firewall ipv4 name V4-INGRESS default-log +set firewall ipv4 name V4-INGRESS rule 101 protocol 'icmp' +set firewall ipv4 name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible' +set firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 100 +set firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 300 +set firewall ipv4 name V4-INGRESS rule 101 log +set firewall ipv4 name V4-INGRESS rule 101 +set firewall ipv4 name V4-INGRESS rule 101 'disable' +set firewall ipv4 name V4-INGRESS rule 101 action 'accept' +set firewall ipv4 name EGRESS default-action 'reject' +set firewall ipv6 name EGRESS default-action 'reject' +set firewall ipv6 name EGRESS rule 20 +set firewall ipv6 name EGRESS rule 20 icmpv6 type-name 'echo-request' +set firewall ipv6 input filter 1 jump-target 'V6-INGRESS' +set firewall ipv6 output filter 1 jump-target 'EGRESS' +set firewall ipv4 input filter 1 jump-target 'INGRESS' +set firewall ipv4 output filter 1 jump-target 'EGRESS' +set firewall ipv4 name IF-TEST rule 10 'disable' +set firewall ipv4 name IF-TEST rule 10 action 'accept' +set firewall ipv4 name IF-TEST rule 10 inbound-interface name 'eth0' +set firewall ipv4 name IF-TEST rule 10 outbound-interface group 'the-ethers' +set firewall ipv4 name IF-TEST rule 10 icmp type-name 'echo-request' +set firewall ipv4 name IF-TEST rule 10 state 'related' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg index bed0b018..175a6567 100644 --- a/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg +++ b/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg @@ -1,8 +1,9 @@ set interfaces ethernet eth0 address 'dhcp' set interfaces ethernet eth0 hw-id '08:00:27:7c:85:05' set interfaces ethernet eth1 description 'test-interface' set interfaces ethernet eth2 hw-id '08:00:27:04:85:99' set interfaces ethernet eth3 hw-id '08:00:27:1c:82:d1' +set interfaces ethernet eth3 disable set interfaces ethernet eth3 description 'Ethernet 3' set interfaces wireguard wg02 description 'wire guard int 2' set interfaces loopback 'lo' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_ospf_interfaces_config_14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_ospf_interfaces_config_14.cfg new file mode 100644 index 00000000..d630d94c --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_ospf_interfaces_config_14.cfg @@ -0,0 +1,4 @@ +set protocols ospfv3 interface eth0 instance-id '33' +set protocols ospfv3 interface eth0 'mtu-ignore' +set protocols ospf interface eth1 cost '100' +set protocols ospfv3 interface eth1 ifmtu '33' diff --git a/tests/unit/modules/network/vyos/test_vyos_facts.py b/tests/unit/modules/network/vyos/test_vyos_facts.py index dd3a7966..7e192e3c 100644 --- a/tests/unit/modules/network/vyos/test_vyos_facts.py +++ b/tests/unit/modules/network/vyos/test_vyos_facts.py @@ -1,109 +1,110 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type import json from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_facts from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosFactsModule(TestVyosModule): module = vyos_facts def setUp(self): super(TestVyosFactsModule, self).setUp() self.mock_run_commands = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base.run_commands", ) self.run_commands = self.mock_run_commands.start() self.mock_get_resource_connection = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection = self.mock_get_resource_connection.start() self.mock_get_capabilities = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base.get_capabilities", ) self.get_capabilities = self.mock_get_capabilities.start() self.get_capabilities.return_value = { "device_info": { "network_os": "vyos", "network_os_hostname": "vyos01", "network_os_model": "VMware", "network_os_version": "VyOS 1.1.7", + "network_os_major_version": "1.1", }, "network_api": "cliconf", } def tearDown(self): super(TestVyosFactsModule, self).tearDown() self.mock_run_commands.stop() self.mock_get_capabilities.stop() self.mock_get_resource_connection.stop() def load_fixtures(self, commands=None, filename=None): def load_from_file(*args, **kwargs): module, commands = args output = list() for item in commands: try: obj = json.loads(item) command = obj["command"] except ValueError: command = item filename = str(command).replace(" ", "_") output.append(load_fixture(filename)) return output self.run_commands.side_effect = load_from_file def test_vyos_facts_default(self): set_module_args(dict(gather_subset="default")) result = self.execute_module() facts = result.get("ansible_facts") self.assertEqual(len(facts), 10) self.assertEqual(facts["ansible_net_hostname"].strip(), "vyos01") self.assertEqual(facts["ansible_net_version"], "VyOS 1.1.7") def test_vyos_facts_not_all(self): set_module_args(dict(gather_subset="!all")) result = self.execute_module() facts = result.get("ansible_facts") self.assertEqual(len(facts), 10) self.assertEqual(facts["ansible_net_hostname"].strip(), "vyos01") self.assertEqual(facts["ansible_net_version"], "VyOS 1.1.7") def test_vyos_facts_exclude_most(self): set_module_args(dict(gather_subset=["!neighbors", "!config"])) result = self.execute_module() facts = result.get("ansible_facts") self.assertEqual(len(facts), 10) self.assertEqual(facts["ansible_net_hostname"].strip(), "vyos01") self.assertEqual(facts["ansible_net_version"], "VyOS 1.1.7") def test_vyos_facts_invalid_subset(self): set_module_args(dict(gather_subset="cereal")) self.execute_module(failed=True) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py index 25c56328..0cc611c4 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py @@ -1,362 +1,488 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_global from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallRulesModule(TestVyosModule): module = vyos_firewall_global def setUp(self): super(TestVyosFirewallRulesModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global.Firewall_globalFacts.get_device_data", ) + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version" + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.2" + self.execute_show_command = self.mock_execute_show_command.start() def tearDown(self): super(TestVyosFirewallRulesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): def load_from_file(*args, **kwargs): return load_fixture("vyos_firewall_global_config.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_firewall_global_set_01_merged(self): set_module_args( dict( config=dict( validation="strict", config_trap=True, log_martians=True, syn_cookies=True, twa_hazards_protection=True, ping=dict(all=True, broadcast=True), state_policy=[ dict( connection_type="established", action="accept", log=True, + log_level="emerg", ), dict(connection_type="invalid", action="reject"), ], route_redirects=[ dict( afi="ipv4", ip_src_route=True, icmp_redirects=dict(send=True, receive=False), ), ], group=dict( address_group=[ dict( afi="ipv4", name="MGMT-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.1.1"), dict(address="192.0.1.3"), dict(address="192.0.1.5"), ], ), dict( afi="ipv6", name="GOOGLE-DNS-v6", members=[ dict(address="2001:4860:4860::8888"), dict(address="2001:4860:4860::8844"), ], ), ], network_group=[ dict( afi="ipv4", name="MGMT", description="This group has the Management network addresses", members=[dict(address="192.0.1.0/24")], ), dict( afi="ipv6", name="DOCUMENTATION-v6", description="IPv6 Addresses reserved for documentation per RFC 3849", members=[ dict(address="2001:0DB8::/32"), dict(address="3FFF:FFFF::/32"), ], ), ], port_group=[ dict( name="TELNET", description="This group has the telnet ports", members=[dict(port="23")], ), ], ), ), state="merged", ), ) commands = [ "set firewall group address-group MGMT-HOSTS address 192.0.1.1", "set firewall group address-group MGMT-HOSTS address 192.0.1.3", "set firewall group address-group MGMT-HOSTS address 192.0.1.5", "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address lists'", "set firewall group address-group MGMT-HOSTS", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8888", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8844", "set firewall group ipv6-address-group GOOGLE-DNS-v6", "set firewall group network-group MGMT network 192.0.1.0/24", "set firewall group network-group MGMT description 'This group has the Management network addresses'", "set firewall group network-group MGMT", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 2001:0DB8::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 3FFF:FFFF::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 description 'IPv6 Addresses reserved for documentation per RFC 3849'", "set firewall group ipv6-network-group DOCUMENTATION-v6", "set firewall group port-group TELNET port 23", "set firewall group port-group TELNET description 'This group has the telnet ports'", "set firewall group port-group TELNET", "set firewall ip-src-route 'enable'", "set firewall receive-redirects 'disable'", "set firewall send-redirects 'enable'", "set firewall config-trap 'enable'", "set firewall state-policy established action 'accept'", "set firewall state-policy established log 'enable'", "set firewall state-policy invalid action 'reject'", "set firewall broadcast-ping 'enable'", "set firewall all-ping 'enable'", "set firewall log-martians 'enable'", "set firewall twa-hazards-protection 'enable'", "set firewall syn-cookies 'enable'", "set firewall source-validation 'strict'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_merged_idem(self): set_module_args( dict( config=dict( group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_global_set_01_replaced(self): set_module_args( dict( config=dict( group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.7"), dict(address="192.0.2.9"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::2"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="2222")], ), ], ), ), state="replaced", ), ) commands = [ "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", "set firewall group port-group SSH port 2222", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_replaced_idem(self): set_module_args( dict( config=dict( group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_global_set_01_deleted(self): set_module_args(dict(config=dict(), state="deleted")) - commands = ["delete firewall "] + commands = ["delete firewall"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_global_set_01_replaced_version(self): + self.get_os_version.return_value = "1.4" + set_module_args( + dict( + config=dict( + validation="strict", + config_trap=True, + log_martians=True, + syn_cookies=True, + twa_hazards_protection=True, + ping=dict(all=True, broadcast=True), + state_policy=[ + dict( + connection_type="established", + action="accept", + log=True, + ), + dict(connection_type="invalid", action="reject"), + ], + route_redirects=[ + dict( + afi="ipv4", + ip_src_route=True, + icmp_redirects=dict(send=True, receive=False), + ), + dict( + afi="ipv6", + ip_src_route=True, + icmp_redirects=dict(receive=False), + ) + ], + group=dict( + address_group=[ + dict( + afi="ipv4", + name="MGMT-HOSTS", + description="This group has the Management hosts address lists", + members=[ + dict(address="192.0.1.1"), + dict(address="192.0.1.3"), + dict(address="192.0.1.5"), + ], + ), + dict( + afi="ipv6", + name="GOOGLE-DNS-v6", + members=[ + dict(address="2001:4860:4860::8888"), + dict(address="2001:4860:4860::8844"), + ], + ), + ], + network_group=[ + dict( + afi="ipv4", + name="MGMT", + description="This group has the Management network addresses", + members=[dict(address="192.0.1.0/24")], + ), + dict( + afi="ipv6", + name="DOCUMENTATION-v6", + description="IPv6 Addresses reserved for documentation per RFC 3849", + members=[ + dict(address="2001:0DB8::/32"), + dict(address="3FFF:FFFF::/32"), + ], + ), + ], + port_group=[ + dict( + name="TELNET", + description="This group has the telnet ports", + members=[dict(port="23")], + ) + ], + ), + ), + state="merged", + ) + ) + commands = [ + "set firewall group address-group MGMT-HOSTS address 192.0.1.1", + "set firewall group address-group MGMT-HOSTS address 192.0.1.3", + "set firewall group address-group MGMT-HOSTS address 192.0.1.5", + "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address lists'", + "set firewall group address-group MGMT-HOSTS", + "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8888", + "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8844", + "set firewall group ipv6-address-group GOOGLE-DNS-v6", + "set firewall group network-group MGMT network 192.0.1.0/24", + "set firewall group network-group MGMT description 'This group has the Management network addresses'", + "set firewall group network-group MGMT", + "set firewall group ipv6-network-group DOCUMENTATION-v6 network 2001:0DB8::/32", + "set firewall group ipv6-network-group DOCUMENTATION-v6 network 3FFF:FFFF::/32", + "set firewall group ipv6-network-group DOCUMENTATION-v6 description 'IPv6 Addresses reserved for documentation per RFC 3849'", + "set firewall group ipv6-network-group DOCUMENTATION-v6", + "set firewall group port-group TELNET port 23", + "set firewall group port-group TELNET description 'This group has the telnet ports'", + "set firewall group port-group TELNET", + "set firewall global-options ip-src-route 'enable'", + "set firewall global-options receive-redirects 'disable'", + "set firewall global-options send-redirects 'enable'", + "set firewall global-options config-trap 'enable'", + "set firewall global-options ipv6-src-route 'enable'", + "set firewall global-options ipv6-receive-redirects 'disable'", + "set firewall global-options state-policy established action 'accept'", + "set firewall global-options state-policy established log 'enable'", + "set firewall global-options state-policy invalid action 'reject'", + "set firewall global-options broadcast-ping 'enable'", + "set firewall global-options all-ping 'enable'", + "set firewall global-options log-martians 'enable'", + "set firewall global-options twa-hazards-protection 'enable'", + "set firewall global-options syn-cookies 'enable'", + "set firewall global-options source-validation 'strict'", + ] self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py similarity index 72% copy from tests/unit/modules/network/vyos/test_vyos_firewall_global.py copy to tests/unit/modules/network/vyos/test_vyos_firewall_global14.py index 25c56328..c594a1fe 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py @@ -1,362 +1,460 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_global from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture -class TestVyosFirewallRulesModule(TestVyosModule): +class TestVyosFirewallRulesModule14(TestVyosModule): module = vyos_firewall_global def setUp(self): - super(TestVyosFirewallRulesModule, self).setUp() + super(TestVyosFirewallRulesModule14, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global.Firewall_globalFacts.get_device_data", ) + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version" + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.4" + self.execute_show_command = self.mock_execute_show_command.start() + self.maxDiff = None def tearDown(self): - super(TestVyosFirewallRulesModule, self).tearDown() + super(TestVyosFirewallRulesModule14, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): def load_from_file(*args, **kwargs): - return load_fixture("vyos_firewall_global_config.cfg") + return load_fixture("vyos_firewall_global_config_v14.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_firewall_global_set_01_merged(self): set_module_args( dict( config=dict( validation="strict", config_trap=True, log_martians=True, syn_cookies=True, twa_hazards_protection=True, ping=dict(all=True, broadcast=True), state_policy=[ dict( connection_type="established", action="accept", log=True, + log_level="emerg", ), dict(connection_type="invalid", action="reject"), ], route_redirects=[ dict( afi="ipv4", ip_src_route=True, icmp_redirects=dict(send=True, receive=False), ), + dict( + afi="ipv6", + ip_src_route=True, + icmp_redirects=dict(receive=False), + ) ], group=dict( address_group=[ dict( afi="ipv4", name="MGMT-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.1.1"), dict(address="192.0.1.3"), dict(address="192.0.1.5"), ], ), dict( afi="ipv6", name="GOOGLE-DNS-v6", members=[ dict(address="2001:4860:4860::8888"), dict(address="2001:4860:4860::8844"), ], ), ], network_group=[ dict( afi="ipv4", name="MGMT", description="This group has the Management network addresses", members=[dict(address="192.0.1.0/24")], ), dict( afi="ipv6", name="DOCUMENTATION-v6", description="IPv6 Addresses reserved for documentation per RFC 3849", members=[ dict(address="2001:0DB8::/32"), dict(address="3FFF:FFFF::/32"), ], ), ], port_group=[ dict( name="TELNET", description="This group has the telnet ports", members=[dict(port="23")], - ), + ) ], ), ), state="merged", - ), + ) ) commands = [ "set firewall group address-group MGMT-HOSTS address 192.0.1.1", "set firewall group address-group MGMT-HOSTS address 192.0.1.3", "set firewall group address-group MGMT-HOSTS address 192.0.1.5", "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address lists'", "set firewall group address-group MGMT-HOSTS", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8888", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8844", "set firewall group ipv6-address-group GOOGLE-DNS-v6", "set firewall group network-group MGMT network 192.0.1.0/24", "set firewall group network-group MGMT description 'This group has the Management network addresses'", "set firewall group network-group MGMT", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 2001:0DB8::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 3FFF:FFFF::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 description 'IPv6 Addresses reserved for documentation per RFC 3849'", "set firewall group ipv6-network-group DOCUMENTATION-v6", "set firewall group port-group TELNET port 23", "set firewall group port-group TELNET description 'This group has the telnet ports'", "set firewall group port-group TELNET", - "set firewall ip-src-route 'enable'", - "set firewall receive-redirects 'disable'", - "set firewall send-redirects 'enable'", - "set firewall config-trap 'enable'", - "set firewall state-policy established action 'accept'", - "set firewall state-policy established log 'enable'", - "set firewall state-policy invalid action 'reject'", - "set firewall broadcast-ping 'enable'", - "set firewall all-ping 'enable'", - "set firewall log-martians 'enable'", - "set firewall twa-hazards-protection 'enable'", - "set firewall syn-cookies 'enable'", - "set firewall source-validation 'strict'", + "set firewall global-options ip-src-route 'enable'", + "set firewall global-options receive-redirects 'disable'", + "set firewall global-options send-redirects 'enable'", + "set firewall global-options config-trap 'enable'", + "set firewall global-options ipv6-src-route 'enable'", + "set firewall global-options ipv6-receive-redirects 'disable'", + "set firewall global-options state-policy established action 'accept'", + "set firewall global-options state-policy established log 'enable'", + "set firewall global-options state-policy established log-level 'emerg'", + "set firewall global-options state-policy invalid action 'reject'", + "set firewall global-options broadcast-ping 'enable'", + "set firewall global-options log-martians 'enable'", + "set firewall global-options twa-hazards-protection 'enable'", + "set firewall global-options syn-cookies 'enable'", + "set firewall global-options source-validation 'strict'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_merged_idem(self): set_module_args( dict( config=dict( group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_global_set_01_replaced(self): set_module_args( dict( config=dict( + state_policy=[ + dict(connection_type="invalid", action="reject"), + ], group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.7"), dict(address="192.0.2.9"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::2"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="2222")], ), ], ), ), state="replaced", ), ) commands = [ "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", + "delete firewall global-options all-ping", + "delete firewall global-options state-policy related", + "set firewall global-options state-policy invalid action 'reject'", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", "set firewall group port-group SSH port 2222", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_replaced_idem(self): set_module_args( dict( config=dict( + ping=dict(all=True), + state_policy=[ + dict(connection_type="related", action="accept", log_level="alert"), + ], group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="replaced", ), ) self.execute_module(changed=False, commands=[]) + def test_vyos_firewall_global_set_02_replaced(self): + set_module_args( + dict( + config=dict( + state_policy=[ + dict(connection_type="invalid", action="reject"), + dict(connection_type="related", action="drop"), + ], + group=dict( + address_group=[ + dict( + afi="ipv4", + name="RND-HOSTS", + description="This group has the Management hosts address lists", + members=[ + dict(address="192.0.2.1"), + dict(address="192.0.2.7"), + dict(address="192.0.2.9"), + ], + ), + dict( + afi="ipv6", + name="LOCAL-v6", + description="This group has the hosts address lists of this machine", + members=[ + dict(address="::1"), + dict(address="fdec:2503:89d6:59b3::2"), + ], + ), + ], + network_group=[ + dict( + afi="ipv4", + name="RND", + description="This group has the Management network addresses", + members=[dict(address="192.0.2.0/24")], + ), + dict( + afi="ipv6", + name="UNIQUE-LOCAL-v6", + description="This group encompasses the ULA address space in IPv6", + members=[dict(address="fc00::/7")], + ), + ], + port_group=[ + dict( + name="SSH", + description="This group has the ssh ports", + members=[dict(port="2222")], + ), + ], + ), + ), + state="replaced", + ), + ) + commands = [ + "delete firewall group address-group RND-HOSTS address 192.0.2.3", + "delete firewall group address-group RND-HOSTS address 192.0.2.5", + "delete firewall global-options all-ping", + "set firewall global-options state-policy related action 'drop'", + "delete firewall global-options state-policy related log-level", + "set firewall global-options state-policy invalid action 'reject'", + "set firewall group address-group RND-HOSTS address 192.0.2.7", + "set firewall group address-group RND-HOSTS address 192.0.2.9", + "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", + "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", + "delete firewall group port-group SSH port 22", + "set firewall group port-group SSH port 2222", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_firewall_global_set_01_deleted(self): set_module_args(dict(config=dict(), state="deleted")) - commands = ["delete firewall "] + commands = ["delete firewall global-options"] self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py index b43b11c7..c0815bfa 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py @@ -1,1184 +1,1706 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_rules from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallRulesModule(TestVyosModule): module = vyos_firewall_rules def setUp(self): super(TestVyosFirewallRulesModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data", ) self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules.Firewall_rulesFacts.get_device_data", ) self.execute_show_command = self.mock_execute_show_command.start() self.mock_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.Firewall_rules._get_os_version", + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.get_os_version", ) self.get_os_version = self.mock_get_os_version.start() - self.get_os_version.return_value = "Vyos 1.2" + self.get_os_version.return_value = "1.2" def tearDown(self): super(TestVyosFirewallRulesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() self.mock_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): def load_from_file(*args, **kwargs): return load_fixture("vyos_firewall_rules_config.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_firewall_rule_set_01_merged(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="V6-INBOUND", description="This is IPv6 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V6-OUTBOUND", description="This is IPv6 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), dict( afi="ipv4", rule_sets=[ dict( name="V4-INBOUND", description="This is IPv4 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V4-OUTBOUND", description="This is IPv4 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), ], state="merged", ), ) commands = [ "set firewall ipv6-name V6-INBOUND default-action 'reject'", "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", "set firewall ipv6-name V6-INBOUND enable-default-log", "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", "set firewall name V4-INBOUND default-action 'reject'", "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", "set firewall name V4-INBOUND enable-default-log", "set firewall name V4-OUTBOUND default-action 'accept'", "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_02_merged(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="V6-INBOUND", description="This is IPv6 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V6-OUTBOUND", description="This is IPv6 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), dict( afi="ipv4", rule_sets=[ dict( name="V4-INBOUND", description="This is IPv4 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V4-OUTBOUND", description="This is IPv4 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), ], state="merged", ), ) commands = [ "set firewall ipv6-name V6-INBOUND default-action 'reject'", "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", "set firewall ipv6-name V6-INBOUND enable-default-log", "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", "set firewall name V4-INBOUND default-action 'reject'", "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", "set firewall name V4-INBOUND enable-default-log", "set firewall name V4-OUTBOUND default-action 'accept'", "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", description="This is IPv4 INBOUND rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", log="disable", protocol="icmp", fragment="match-frag", disable=True, ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall name INBOUND default-action 'accept'", "set firewall name INBOUND description 'This is IPv4 INBOUND rule set'", "set firewall name INBOUND enable-default-log", "set firewall name INBOUND rule 101 protocol 'icmp'", "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", "set firewall name INBOUND rule 101 fragment 'match-frag'", "set firewall name INBOUND rule 101", "set firewall name INBOUND rule 101 disable", "set firewall name INBOUND rule 101 action 'accept'", "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", "set firewall name INBOUND rule 101 log 'disable'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_02(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="tcp", source=dict( address="192.0.2.0", mac_address="38:00:25:19:76:0c", port=2127, ), destination=dict(address="192.0.1.0", port=2124), limit=dict( burst=10, rate=dict(number=20, unit="second"), ), recent=dict(count=10, time=20), state=dict( established=True, related=True, invalid=True, new=True, ), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall name INBOUND rule 101 protocol 'tcp'", "set firewall name INBOUND rule 101 destination address 192.0.1.0", "set firewall name INBOUND rule 101 destination port 2124", "set firewall name INBOUND rule 101", "set firewall name INBOUND rule 101 source address 192.0.2.0", "set firewall name INBOUND rule 101 source mac-address 38:00:25:19:76:0c", "set firewall name INBOUND rule 101 source port 2127", "set firewall name INBOUND rule 101 state new enable", "set firewall name INBOUND rule 101 state invalid enable", "set firewall name INBOUND rule 101 state related enable", "set firewall name INBOUND rule 101 state established enable", "set firewall name INBOUND rule 101 limit burst 10", "set firewall name INBOUND rule 101 limit rate 20/second", "set firewall name INBOUND rule 101 recent count 10", "set firewall name INBOUND rule 101 recent time 20", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_03(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", destination=dict( group=dict( address_group="OUT-ADDR-GROUP", network_group="OUT-NET-GROUP", port_group="OUT-PORT-GROUP", ), ), source=dict( group=dict( address_group="IN-ADDR-GROUP", network_group="IN-NET-GROUP", port_group="IN-PORT-GROUP", ), ), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall name INBOUND rule 101 source group address-group IN-ADDR-GROUP", "set firewall name INBOUND rule 101 source group network-group IN-NET-GROUP", "set firewall name INBOUND rule 101 source group port-group IN-PORT-GROUP", "set firewall name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP", "set firewall name INBOUND rule 101 destination group network-group OUT-NET-GROUP", "set firewall name INBOUND rule 101 destination group port-group OUT-PORT-GROUP", "set firewall name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_04(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", time=dict( monthdays="2", startdate="2020-01-24", starttime="13:20:00", stopdate="2020-01-28", stoptime="13:30:00", weekdays="!Sat,Sun", utc=True, ), - tcp=dict(flags="ALL"), + tcp=dict( + flags=[ + dict(flag="all"), + ] + ), + ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall name INBOUND rule 101", "set firewall name INBOUND rule 101 tcp flags ALL", "set firewall name INBOUND rule 101 time utc", "set firewall name INBOUND rule 101 time monthdays 2", "set firewall name INBOUND rule 101 time startdate 2020-01-24", "set firewall name INBOUND rule 101 time stopdate 2020-01-28", "set firewall name INBOUND rule 101 time weekdays !Sat,Sun", "set firewall name INBOUND rule 101 time stoptime 13:30:00", "set firewall name INBOUND rule 101 time starttime 13:20:00", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_01(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", description="This is IPv6 INBOUND rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", - disabled=True, + disable=True, icmp=dict(type_name="echo-request"), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall ipv6-name INBOUND default-action 'accept'", "set firewall ipv6-name INBOUND description 'This is IPv6 INBOUND rule set'", "set firewall ipv6-name INBOUND enable-default-log", "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", "set firewall ipv6-name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", "set firewall ipv6-name INBOUND rule 101", "set firewall ipv6-name INBOUND rule 101 disable", "set firewall ipv6-name INBOUND rule 101 action 'accept'", "set firewall ipv6-name INBOUND rule 101 ipsec 'match-ipsec'", "set firewall ipv6-name INBOUND rule 101 icmpv6 type echo-request", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_02(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="tcp", source=dict( address="2001:db8::12", mac_address="38:00:25:19:76:0c", port=2127, ), destination=dict(address="2001:db8::11", port=2124), limit=dict( burst=10, rate=dict(number=20, unit="second"), ), recent=dict(count=10, time=20), state=dict( established=True, related=True, invalid=True, new=True, ), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall ipv6-name INBOUND rule 101 protocol 'tcp'", "set firewall ipv6-name INBOUND rule 101 destination address 2001:db8::11", "set firewall ipv6-name INBOUND rule 101 destination port 2124", "set firewall ipv6-name INBOUND rule 101", "set firewall ipv6-name INBOUND rule 101 source address 2001:db8::12", "set firewall ipv6-name INBOUND rule 101 source mac-address 38:00:25:19:76:0c", "set firewall ipv6-name INBOUND rule 101 source port 2127", "set firewall ipv6-name INBOUND rule 101 state new enable", "set firewall ipv6-name INBOUND rule 101 state invalid enable", "set firewall ipv6-name INBOUND rule 101 state related enable", "set firewall ipv6-name INBOUND rule 101 state established enable", "set firewall ipv6-name INBOUND rule 101 limit burst 10", "set firewall ipv6-name INBOUND rule 101 recent count 10", "set firewall ipv6-name INBOUND rule 101 recent time 20", "set firewall ipv6-name INBOUND rule 101 limit rate 20/second", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_03(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", destination=dict( group=dict( address_group="OUT-ADDR-GROUP", network_group="OUT-NET-GROUP", port_group="OUT-PORT-GROUP", ), ), source=dict( group=dict( address_group="IN-ADDR-GROUP", network_group="IN-NET-GROUP", port_group="IN-PORT-GROUP", ), ), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall ipv6-name INBOUND rule 101 source group address-group IN-ADDR-GROUP", "set firewall ipv6-name INBOUND rule 101 source group network-group IN-NET-GROUP", "set firewall ipv6-name INBOUND rule 101 source group port-group IN-PORT-GROUP", "set firewall ipv6-name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP", "set firewall ipv6-name INBOUND rule 101 destination group network-group OUT-NET-GROUP", "set firewall ipv6-name INBOUND rule 101 destination group port-group OUT-PORT-GROUP", "set firewall ipv6-name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_04(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", time=dict( monthdays="2", startdate="2020-01-24", starttime="13:20:00", stopdate="2020-01-28", stoptime="13:30:00", weekdays="!Sat,Sun", utc=True, ), - tcp=dict(flags="ALL"), + tcp=dict( + flags=[ + dict(flag="all"), + ] + ), ), + dict( + number="102", + tcp=dict( + flags=[ + dict(flag="ack"), + dict(flag="syn"), + dict(flag="fin", invert=True), + ], + ) + ) ], ), ], ), ], state="merged", ), ) commands = [ "set firewall ipv6-name INBOUND rule 101", "set firewall ipv6-name INBOUND rule 101 tcp flags ALL", "set firewall ipv6-name INBOUND rule 101 time utc", "set firewall ipv6-name INBOUND rule 101 time monthdays 2", "set firewall ipv6-name INBOUND rule 101 time startdate 2020-01-24", "set firewall ipv6-name INBOUND rule 101 time stopdate 2020-01-28", "set firewall ipv6-name INBOUND rule 101 time weekdays !Sat,Sun", "set firewall ipv6-name INBOUND rule 101 time stoptime 13:30:00", "set firewall ipv6-name INBOUND rule 101 time starttime 13:20:00", + "set firewall ipv6-name INBOUND rule 102", + "set firewall ipv6-name INBOUND rule 102 tcp flags ACK,SYN,!FIN", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_icmp_01(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="icmp", icmp=dict(type_name="port-unreachable"), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall ipv6-name INBOUND rule 101 icmpv6 type port-unreachable", "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", "set firewall ipv6-name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="icmp", icmp=dict(type=1, code=1), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall name INBOUND rule 101 icmp type 1", "set firewall name INBOUND rule 101 icmp code 1", "set firewall name INBOUND rule 101 protocol 'icmp'", "set firewall name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_02(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="icmp", icmp=dict(type_name="echo-request"), ), ], ), ], ), ], state="merged", ), ) commands = [ "set firewall name INBOUND rule 101 icmp type-name echo-request", "set firewall name INBOUND rule 101 protocol 'icmp'", "set firewall name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_del_01(self): set_module_args( dict( config=[dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS")])], state="deleted", ), ) commands = ["delete firewall name V4-INGRESS"] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_02(self): set_module_args( dict( config=[ dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS")]), dict(afi="ipv6", rule_sets=[dict(name="V6-INGRESS")]), ], state="deleted", ), ) commands = [ "delete firewall name V4-INGRESS", "delete firewall ipv6-name V6-INGRESS", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_03(self): set_module_args(dict(config=[], state="deleted")) commands = ["delete firewall name", "delete firewall ipv6-name"] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_04(self): set_module_args( dict( config=[ dict(afi="ipv4", rule_sets=[dict(name="V4-ING")]), dict(afi="ipv6", rule_sets=[dict(name="V6-ING")]), ], state="deleted", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_rep_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="reject", description="Rule 101 is configured by Ansible RM", ipsec="match-ipsec", protocol="tcp", fragment="match-frag", - disabled=False, + disable=False, ), dict( number="102", action="accept", description="Rule 102 is configured by Ansible RM", protocol="icmp", - disabled=True, + disable=True, ), ], ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-INGRESS", default_action="accept", description="This rule-set is configured by Ansible RM", ), dict( name="EGRESS", default_action="reject", description="This rule-set is configured by Ansible RM", rules=[ dict( icmp=dict(type_name="echo-request"), number=20, ), ], ), ], ), ], state="replaced", ), ) commands = [ "delete firewall name V4-INGRESS rule 101 disable", "set firewall name V4-INGRESS description 'This is IPv4 INGRESS rule set'", "set firewall name V4-INGRESS rule 101 protocol 'tcp'", "set firewall name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible RM'", "set firewall name V4-INGRESS rule 101 action 'reject'", + "delete firewall name V4-INGRESS rule 101 log", "set firewall name V4-INGRESS rule 102 disable", "set firewall name V4-INGRESS rule 102 action 'accept'", "set firewall name V4-INGRESS rule 102 protocol 'icmp'", "set firewall name V4-INGRESS rule 102 description 'Rule 102 is configured by Ansible RM'", "set firewall name V4-INGRESS rule 102", "set firewall ipv6-name V6-INGRESS description 'This rule-set is configured by Ansible RM'", "set firewall ipv6-name EGRESS description 'This rule-set is configured by Ansible RM'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_rep_02(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=False, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", - disabled=True, + disable=True, ), ], ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-INGRESS", default_action="accept", ), dict( name="EGRESS", default_action="reject", rules=[ dict( icmp=dict(type_name="echo-request"), number=20, ), ], ), ], ), ], state="replaced", ), ) commands = [ "delete firewall name V4-INGRESS enable-default-log", + "delete firewall name V4-INGRESS rule 101 log", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", - disabled=True, - ), + disable=True, + log="enable", + ) ], ), dict( name="EGRESS", default_action="reject", ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-INGRESS", default_action="accept", ), dict( name="EGRESS", default_action="reject", rules=[ dict( icmp=dict(type_name="echo-request"), number=20, ), ], ), ], ), ], state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_02(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", - disabled=True, + disable=True, + log="enable" ), ], ), ], ), ], state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_mer_idem_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", - disabled=True, - ), + disable=True, + ) ], ), dict( name="EGRESS", default_action="reject", ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-INGRESS", default_action="accept", ), dict( name="EGRESS", default_action="reject", rules=[ dict( icmp=dict(type_name="echo-request"), number=20, ), ], ), ], ), ], state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-IN", description="This is IPv4 INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="1", action="reject", description="Rule 1 is configured by Ansible RM", ipsec="match-ipsec", log="enable", protocol="tcp", fragment="match-frag", - disabled=False, + disable=False, source=dict( group=dict( address_group="IN-ADDR-GROUP", network_group="IN-NET-GROUP", port_group="IN-PORT-GROUP", ), ), ), dict( number="2", action="accept", description="Rule 102 is configured by Ansible RM", protocol="icmp", - disabled=True, + disable=True, ), ], ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-IN", default_action="accept", description="This rule-set is configured by Ansible RM", ), dict( name="V6-EG", default_action="reject", description="This rule-set is configured by Ansible RM", ), ], ), ], state="overridden", ), ) commands = [ "delete firewall ipv6-name V6-INGRESS", "delete firewall ipv6-name EGRESS", "delete firewall name V4-INGRESS", "delete firewall name EGRESS", "set firewall name V4-IN default-action 'accept'", "set firewall name V4-IN description 'This is IPv4 INGRESS rule set'", "set firewall name V4-IN enable-default-log", "set firewall name V4-IN rule 1 protocol 'tcp'", "set firewall name V4-IN rule 1 log 'enable'", "set firewall name V4-IN rule 1 description 'Rule 1 is configured by Ansible RM'", "set firewall name V4-IN rule 1 fragment 'match-frag'", "set firewall name V4-IN rule 1 source group address-group IN-ADDR-GROUP", "set firewall name V4-IN rule 1 source group network-group IN-NET-GROUP", "set firewall name V4-IN rule 1 source group port-group IN-PORT-GROUP", "set firewall name V4-IN rule 1", "set firewall name V4-IN rule 1 action 'reject'", "set firewall name V4-IN rule 1 ipsec 'match-ipsec'", "set firewall name V4-IN rule 2 disable", "set firewall name V4-IN rule 2 action 'accept'", "set firewall name V4-IN rule 2 protocol 'icmp'", "set firewall name V4-IN rule 2 description 'Rule 102 is configured by Ansible RM'", "set firewall name V4-IN rule 2", "set firewall ipv6-name V6-IN default-action 'accept'", "set firewall ipv6-name V6-IN description 'This rule-set is configured by Ansible RM'", "set firewall ipv6-name V6-EG default-action 'reject'", "set firewall ipv6-name V6-EG description 'This rule-set is configured by Ansible RM'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_idem_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", - disabled=True, - ), + disable=True, + log="enable", + ) ], ), dict( name="EGRESS", default_action="reject", ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-INGRESS", default_action="accept", ), dict( name="EGRESS", default_action="reject", rules=[ dict( icmp=dict(type_name="echo-request"), number=20, ), ], ), ], ), ], state="overridden", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v6_rule_sets_rule_merged_01_version(self): - self.get_os_version.return_value = "VyOS 1.4-rolling-202007010117" + self.get_os_version.return_value = "1.4" set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", description="This is IPv6 INBOUND rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", - disabled=True, + disable=True, icmp=dict(type_name="echo-request"), + log="enable", + ), + dict( + number="102", + action="reject", + description="Rule 102 is configured by Ansible", + protocol="ipv6-icmp", + icmp=dict(type=7), ), ], ), ], ), ], state="merged", ), ) commands = [ - "set firewall ipv6-name INBOUND default-action 'accept'", - "set firewall ipv6-name INBOUND description 'This is IPv6 INBOUND rule set'", - "set firewall ipv6-name INBOUND enable-default-log", - "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", - "set firewall ipv6-name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", - "set firewall ipv6-name INBOUND rule 101", - "set firewall ipv6-name INBOUND rule 101 disable", - "set firewall ipv6-name INBOUND rule 101 action 'accept'", - "set firewall ipv6-name INBOUND rule 101 ipsec 'match-ipsec'", - "set firewall ipv6-name INBOUND rule 101 icmpv6 type-name echo-request", + "set firewall ipv6 name INBOUND default-action 'accept'", + "set firewall ipv6 name INBOUND description 'This is IPv6 INBOUND rule set'", + "set firewall ipv6 name INBOUND default-log", + "set firewall ipv6 name INBOUND rule 101 protocol 'icmp'", + "set firewall ipv6 name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall ipv6 name INBOUND rule 101", + "set firewall ipv6 name INBOUND rule 101 disable", + "set firewall ipv6 name INBOUND rule 101 action 'accept'", + "set firewall ipv6 name INBOUND rule 101 ipsec 'match-ipsec'", + "set firewall ipv6 name INBOUND rule 101 icmpv6 type-name echo-request", + "set firewall ipv6 name INBOUND rule 101 log 'enable'", + "set firewall ipv6 name INBOUND rule 102", + "set firewall ipv6 name INBOUND rule 102 action 'reject'", + "set firewall ipv6 name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", + "set firewall ipv6 name INBOUND rule 102 protocol 'ipv6-icmp'", + 'set firewall ipv6 name INBOUND rule 102 icmpv6 type 7', + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_jump_rules_merged_01(self): + self.get_os_version.return_value = "1.4" + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + description="This is IPv6 INBOUND rule set with a jump action", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="jump", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + icmp=dict(type_name="echo-request"), + jump_target="PROTECT-RE", + packet_length_exclude=[dict(length=100), dict(length=200)] + ), + dict( + number="102", + action="reject", + description="Rule 102 is configured by Ansible", + protocol="ipv6-icmp", + icmp=dict(type=7), + ), + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6 name INBOUND default-action 'accept'", + "set firewall ipv6 name INBOUND description 'This is IPv6 INBOUND rule set with a jump action'", + "set firewall ipv6 name INBOUND default-log", + "set firewall ipv6 name INBOUND rule 101 protocol 'icmp'", + "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 100", + "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 200", + "set firewall ipv6 name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall ipv6 name INBOUND rule 101", + "set firewall ipv6 name INBOUND rule 101 ipsec 'match-ipsec'", + "set firewall ipv6 name INBOUND rule 101 icmpv6 type-name echo-request", + "set firewall ipv6 name INBOUND rule 101 action 'jump'", + "set firewall ipv6 name INBOUND rule 101 jump-target 'PROTECT-RE'", + "set firewall ipv6 name INBOUND rule 102", + "set firewall ipv6 name INBOUND rule 102 action 'reject'", + "set firewall ipv6 name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", + "set firewall ipv6 name INBOUND rule 102 protocol 'ipv6-icmp'", + 'set firewall ipv6 name INBOUND rule 102 icmpv6 type 7', + ] + self.execute_module(changed=True, commands=commands) + + +class TestVyosFirewallRulesModule14(TestVyosModule): + module = vyos_firewall_rules + + def setUp(self): + super(TestVyosFirewallRulesModule14, self).setUp() + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection" + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data" + ) + + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules.Firewall_rulesFacts.get_device_data" + ) + self.execute_show_command = self.mock_execute_show_command.start() + + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.get_os_version" + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.4" + self.maxDiff = None + + def tearDown(self): + super(TestVyosFirewallRulesModule14, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + + def load_fixtures(self, commands=None, filename=None): + def load_from_file(*args, **kwargs): + return load_fixture("vyos_firewall_rules_config_v14.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_firewall_packet_length_merged_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + description="This is IPv6 INBOUND rule set with a jump action", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="jump", + description="Rule 101 is configured by Ansible", + jump_target="PROTECT-RE", + packet_length_exclude=[dict(length=100), dict(length=200)], + packet_length=[dict(length=22)] + ), + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6 name INBOUND default-action 'accept'", + "set firewall ipv6 name INBOUND description 'This is IPv6 INBOUND rule set with a jump action'", + "set firewall ipv6 name INBOUND default-log", + "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 100", + "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 200", + "set firewall ipv6 name INBOUND rule 101 packet-length 22", + "set firewall ipv6 name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall ipv6 name INBOUND rule 101", + "set firewall ipv6 name INBOUND rule 101 action 'jump'", + "set firewall ipv6 name INBOUND rule 101 jump-target 'PROTECT-RE'", + ] + self.maxDiff = None + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_packet_length_replace_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INGRESS", + description="This is IPv4 V4-INGRESS rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + packet_length_exclude=[dict(length=100), dict(length=200)], + packet_length=[dict(length=22)] + ), + ], + ), + ], + ) + ], + state="replaced", + ) + ) + commands = [ + "delete firewall ipv4 name V4-INGRESS rule 101 protocol", + "delete firewall ipv4 name V4-INGRESS rule 101 disable", + "delete firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 300", + "set firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 200", + "set firewall ipv4 name V4-INGRESS rule 101 packet-length 22", + ] + self.maxDiff = None + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_filter_merged_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + filter="input", + description="This is IPv6 INBOUND rule set with a jump action", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="jump", + description="Rule 101 is configured by Ansible", + jump_target="PROTECT-RE", + packet_length_exclude=[dict(length=100), dict(length=200)], + packet_length=[dict(length=22)] + ), + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6 input filter default-action 'accept'", + "set firewall ipv6 input filter description 'This is IPv6 INBOUND rule set with a jump action'", + "set firewall ipv6 input filter default-log", + "set firewall ipv6 input filter rule 101 packet-length-exclude 100", + "set firewall ipv6 input filter rule 101 packet-length-exclude 200", + "set firewall ipv6 input filter rule 101 packet-length 22", + "set firewall ipv6 input filter rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall ipv6 input filter rule 101", + "set firewall ipv6 input filter rule 101 action 'jump'", + "set firewall ipv6 input filter rule 101 jump-target 'PROTECT-RE'", + ] + self.maxDiff = None + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_interface_merged_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="V6-INGRESS", + description="This is IPv6 INBOUND rule set with a jump action", + default_action="accept", + rules=[ + dict( + number="101", + action="jump", + description="Rule 101 is configured by Ansible", + jump_target="PROTECT-RE", + inbound_interface=dict(name="eth0"), + outbound_interface=dict(group="eth1"), + ), + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6 name V6-INGRESS description 'This is IPv6 INBOUND rule set with a jump action'", + "set firewall ipv6 name V6-INGRESS rule 101 inbound-interface name eth0", + "set firewall ipv6 name V6-INGRESS rule 101 outbound-interface group eth1", + "set firewall ipv6 name V6-INGRESS rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall ipv6 name V6-INGRESS rule 101", + "set firewall ipv6 name V6-INGRESS rule 101 action 'jump'", + "set firewall ipv6 name V6-INGRESS rule 101 jump-target 'PROTECT-RE'", + ] + self.maxDiff = None + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_interface_replace_02(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="IF-TEST", + description="Changed", + rules=[ + dict( + number="10", + action="accept", + description="Rule 10 is configured by Ansible", + inbound_interface=dict(name="eth1"), + ), + ], + ), + ], + ) + ], + state="replaced", + ) + ) + commands = [ + "set firewall ipv4 name IF-TEST description 'Changed'", + "set firewall ipv4 name IF-TEST rule 10 description 'Rule 10 is configured by Ansible'", + 'set firewall ipv4 name IF-TEST rule 10 inbound-interface name eth1', + "delete firewall ipv4 name IF-TEST rule 10 outbound-interface group", + "delete firewall ipv4 name IF-TEST rule 10 disable", + "delete firewall ipv4 name IF-TEST rule 10 state related", + "delete firewall ipv4 name IF-TEST rule 10 icmp type-name echo-request", + ] + self.maxDiff = None + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_02(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + protocol="tcp", + source=dict( + address="192.0.2.0", + mac_address="38:00:25:19:76:0c", + port=2127, + ), + destination=dict(address="192.0.1.0", port=2124), + limit=dict( + burst=10, + rate=dict(number=20, unit="second"), + ), + recent=dict(count=10, time=20), + state=dict( + established=True, + related=True, + invalid=True, + new=True, + ), + ), + ], + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set firewall ipv4 name INBOUND rule 101 protocol 'tcp'", + "set firewall ipv4 name INBOUND rule 101 destination port 2124", + "set firewall ipv4 name INBOUND rule 101", + "set firewall ipv4 name INBOUND rule 101 destination address 192.0.1.0", + "set firewall ipv4 name INBOUND rule 101 source address 192.0.2.0", + "set firewall ipv4 name INBOUND rule 101 source mac-address 38:00:25:19:76:0c", + "set firewall ipv4 name INBOUND rule 101 source port 2127", + "set firewall ipv4 name INBOUND rule 101 state new", + "set firewall ipv4 name INBOUND rule 101 state invalid", + "set firewall ipv4 name INBOUND rule 101 state related", + "set firewall ipv4 name INBOUND rule 101 state established", + "set firewall ipv4 name INBOUND rule 101 limit burst 10", + "set firewall ipv4 name INBOUND rule 101 limit rate 20/second", + "set firewall ipv4 name INBOUND rule 101 recent count 10", + "set firewall ipv4 name INBOUND rule 101 recent time 20", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_change_state_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="IF-TEST", + rules=[ + dict( + number="10", + disable=False, + action="accept", + state=dict( + established=True, + new=True, + ), + ), + ], + ), + ], + ), + ], + state="replaced", + ), + ) + commands = [ + "delete firewall ipv4 name IF-TEST rule 10 disable", + "delete firewall ipv4 name IF-TEST rule 10 inbound-interface name", + "delete firewall ipv4 name IF-TEST rule 10 icmp type-name echo-request", + "delete firewall ipv4 name IF-TEST rule 10 outbound-interface group", + "delete firewall ipv4 name IF-TEST rule 10 state related", + "set firewall ipv4 name IF-TEST rule 10 state established", + "set firewall ipv4 name IF-TEST rule 10 state new", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_del_03(self): + set_module_args(dict(config=[], state="deleted")) + commands = ["delete firewall ipv4", "delete firewall ipv6"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_04(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + time=dict( + monthdays="2", + startdate="2020-01-24", + starttime="13:20:00", + stopdate="2020-01-28", + stoptime="13:30:00", + weekdays="!Sat,Sun", + utc=True, + ), + tcp=dict( + flags=[ + dict(flag="all"), + ] + ), + ), + dict( + number="102", + tcp=dict( + flags=[ + dict(flag="ack"), + dict(flag="syn"), + dict(flag="fin", invert=True), + ], + ) + ) + ], + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set firewall ipv6 name INBOUND rule 101", + "set firewall ipv6 name INBOUND rule 101 tcp flags all", + "set firewall ipv6 name INBOUND rule 101 time utc", + "set firewall ipv6 name INBOUND rule 101 time monthdays 2", + "set firewall ipv6 name INBOUND rule 101 time startdate 2020-01-24", + "set firewall ipv6 name INBOUND rule 101 time stopdate 2020-01-28", + "set firewall ipv6 name INBOUND rule 101 time weekdays !Sat,Sun", + "set firewall ipv6 name INBOUND rule 101 time stoptime 13:30:00", + "set firewall ipv6 name INBOUND rule 101 time starttime 13:20:00", + "set firewall ipv6 name INBOUND rule 102", + "set firewall ipv6 name INBOUND rule 102 tcp flags ack", + "set firewall ipv6 name INBOUND rule 102 tcp flags not fin", + "set firewall ipv6 name INBOUND rule 102 tcp flags syn", ] self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_interfaces.py index affb4f81..14e49c36 100644 --- a/tests/unit/modules/network/vyos/test_vyos_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_interfaces.py @@ -1,187 +1,222 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_interfaces from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallInterfacesModule(TestVyosModule): module = vyos_interfaces def setUp(self): super(TestVyosFirewallInterfacesModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos." "facts.interfaces.interfaces.InterfacesFacts.get_device_data", ) self.execute_show_command = self.mock_execute_show_command.start() def tearDown(self): super(TestVyosFirewallInterfacesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() def load_fixtures(self, commands=None, filename=None): def load_from_file(*args, **kwargs): return load_fixture("vyos_interfaces_config.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_interfaces_merged(self): set_module_args( dict( config=[ dict(name="bond1", description="Bond - 1", enabled=True), dict(name="vtun1", description="vtun - 1", enabled=True), dict(name="wg01", description="wg - 1", enabled=True), ], state="merged", ), ) commands = [ "set interfaces bonding bond1 description 'Bond - 1'", "set interfaces openvpn vtun1 description 'vtun - 1'", "set interfaces wireguard wg01 description 'wg - 1'", ] self.execute_module(changed=True, commands=commands) def test_vyos_interfaces_merged_idempotent(self): set_module_args( dict( config=[ dict( name="wg02", description="wire guard int 2", enabled=True, ), ], state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_interfaces_merged_newinterface(self): set_module_args( dict( config=[ dict( name="eth4", description="Ethernet 4", enabled=True, speed="auto", duplex="auto", ), dict(name="eth1", description="Configured by Ansible"), ], state="merged", ), ) commands = [ "set interfaces ethernet eth1 description 'Configured by Ansible'", "set interfaces ethernet eth4 description 'Ethernet 4'", "set interfaces ethernet eth4 duplex 'auto'", "set interfaces ethernet eth4 speed 'auto'", ] self.execute_module(changed=True, commands=commands) def test_vyos_interfaces_replaced_newinterface(self): set_module_args( dict( config=[ dict( name="eth4", description="Ethernet 4", enabled=True, speed="auto", duplex="auto", ), dict(name="eth1", description="Configured by Ansible"), ], state="replaced", ), ) commands = [ "set interfaces ethernet eth1 description 'Configured by Ansible'", "set interfaces ethernet eth4 description 'Ethernet 4'", "set interfaces ethernet eth4 duplex 'auto'", "set interfaces ethernet eth4 speed 'auto'", ] self.execute_module(changed=True, commands=commands) def test_vyos_interfaces_overridden_newinterface(self): set_module_args( dict( config=[ dict( name="eth4", description="Ethernet 4", enabled=True, speed="auto", duplex="auto", ), dict(name="eth1", description="Configured by Ansible"), ], state="overridden", ), ) commands = [ "set interfaces ethernet eth1 description 'Configured by Ansible'", "set interfaces ethernet eth4 description 'Ethernet 4'", "set interfaces ethernet eth4 duplex 'auto'", "set interfaces ethernet eth4 speed 'auto'", "delete interfaces wireguard wg02 description", "delete interfaces ethernet eth3 description", + "delete interfaces ethernet eth3 disable", ] self.execute_module(changed=True, commands=commands) + + def test_vyos_interfaces_idempotent_disable(self): + set_module_args( + dict( + config=[ + dict( + name="eth3", + description="Ethernet 3", + enabled=False, + ), + ], + state="merged", + ) + ) + + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_vyos_interfaces_idempotent_disable_replace(self): + set_module_args( + dict( + config=[ + dict( + name="eth3", + description="Ethernet 3", + enabled=False, + ), + ], + state="replaced", + ) + ) + + commands = [] + self.execute_module(changed=False, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_logging_global.py b/tests/unit/modules/network/vyos/test_vyos_logging_global.py index 872769e2..a6751518 100644 --- a/tests/unit/modules/network/vyos/test_vyos_logging_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_logging_global.py @@ -1,452 +1,451 @@ # # (c) 2021, Ansible by Red Hat, inc # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # from __future__ import absolute_import, division, print_function __metaclass__ = type from textwrap import dedent from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_logging_global from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule class TestVyosLoggingGlobalModule(TestVyosModule): module = vyos_logging_global def setUp(self): super(TestVyosLoggingGlobalModule, self).setUp() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.logging_global.logging_global.Logging_globalFacts.get_logging_data", ) self.execute_show_command = self.mock_execute_show_command.start() def tearDown(self): super(TestVyosLoggingGlobalModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_execute_show_command.stop() def test_vyos_logging_global_merged_idempotent(self): self.execute_show_command.return_value = dedent( """\ set system syslog console facility all set system syslog console facility local7 level 'err' set system syslog console facility news level 'debug' set system syslog file xyz set system syslog file abc archive size '125' set system syslog file def archive file '2' set system syslog file def facility local6 level 'emerg' set system syslog file def facility local7 level 'emerg' set system syslog global archive file '2' set system syslog global archive size '111' set system syslog global facility cron level 'debug' set system syslog global facility local7 level 'debug' set system syslog global marker interval '111' set system syslog global preserve-fqdn set system syslog host 10.0.2.12 facility all protocol 'udp' set system syslog host 10.0.2.15 facility all level 'all' set system syslog host 10.0.2.15 facility all protocol 'udp' set system syslog host 10.0.2.15 port '122' set system syslog user paul facility local7 level 'err' set system syslog user vyos facility local6 level 'alert' set system syslog user vyos facility local7 level 'debug' """, ) playbook = dict( config=dict( console=dict( facilities=[ dict(facility="all"), dict(facility="local7", severity="err"), dict(facility="news", severity="debug"), ], ), files=[ dict(path="xyz"), dict(path="abc", archive=dict(size=125)), dict( path="def", archive=dict(file_num=2), facilities=[ dict(facility="local6", severity="emerg"), dict(facility="local7", severity="emerg"), ], ), ], hosts=[ dict( hostname="10.0.2.15", port=122, facilities=[ dict(facility="all", severity="all"), dict(facility="all", protocol="udp"), ], ), dict( hostname="10.0.2.12", facilities=[dict(facility="all", protocol="udp")], ), ], users=[ dict( username="vyos", facilities=[ dict(facility="local7", severity="debug"), dict(facility="local6", severity="alert"), ], ), dict( username="paul", facilities=[dict(facility="local7", severity="err")], ), ], global_params=dict( archive=dict(size=111, file_num=2), marker_interval=111, preserve_fqdn="True", facilities=[ dict(facility="cron", severity="debug"), dict(facility="local7", severity="debug"), ], ), ), ) compare_cmds = [] playbook["state"] = "merged" set_module_args(playbook) result = self.execute_module() self.maxDiff = None self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) def test_vyos_logging_global_merged(self): self.execute_show_command.return_value = dedent( """\ """, ) playbook = dict( config=dict( console=dict( facilities=[ dict(facility="all"), dict(facility="local7", severity="err"), dict(facility="news", severity="debug"), ], ), files=[ dict(path="xyz"), dict(path="abc", archive=dict(size=125)), dict( path="def", archive=dict(file_num=2), facilities=[ dict(facility="local6", severity="emerg"), dict(facility="local7", severity="emerg"), ], ), ], hosts=[ dict( hostname="10.0.2.15", port=122, facilities=[ dict(facility="all", severity="all"), dict(facility="all", protocol="udp"), ], ), dict( hostname="10.0.2.12", facilities=[dict(facility="all", protocol="udp")], ), ], users=[ dict( username="vyos", facilities=[ dict(facility="local7", severity="debug"), dict(facility="local6", severity="alert"), ], ), dict( username="paul", facilities=[dict(facility="local7", severity="err")], ), ], global_params=dict( archive=dict(size=111, file_num=2), marker_interval=111, preserve_fqdn="True", facilities=[ dict(facility="cron", severity="debug"), dict(facility="local7", severity="debug"), ], ), ), ) compare_cmds = [ "set system syslog user paul facility local7 level err", "set system syslog host 10.0.2.15 facility all protocol udp", "set system syslog global marker interval 111", "set system syslog file def archive file 2", "set system syslog file abc archive size 125", "set system syslog console facility local7 level err", "set system syslog host 10.0.2.12 facility all protocol udp", "set system syslog global facility local7 level debug", "set system syslog host 10.0.2.15 facility all level all", "set system syslog global preserve-fqdn", "set system syslog global archive file 2", "set system syslog console facility all", "set system syslog file xyz", "set system syslog file def facility local7 level emerg", "set system syslog console facility news level debug", "set system syslog user vyos facility local6 level alert", "set system syslog global facility cron level debug", "set system syslog file def facility local6 level emerg", "set system syslog global archive size 111", "set system syslog host 10.0.2.15 port 122", "set system syslog user vyos facility local7 level debug", ] playbook["state"] = "merged" set_module_args(playbook) result = self.execute_module(changed=True) self.maxDiff = None self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) def test_vyos_logging_global_deleted(self): self.execute_show_command.return_value = dedent( """\ set system syslog console facility all set system syslog console facility local7 level 'err' set system syslog console facility news level 'debug' set system syslog file xyz set system syslog file abc archive size '125' set system syslog file def archive file '2' set system syslog file def facility local6 level 'emerg' set system syslog file def facility local7 level 'emerg' set system syslog global archive file '2' set system syslog global archive size '111' set system syslog global facility cron level 'debug' set system syslog global facility local7 level 'debug' set system syslog global marker interval '111' set system syslog global preserve-fqdn set system syslog host 10.0.2.12 facility all protocol 'udp' set system syslog host 10.0.2.15 facility all level 'all' set system syslog host 10.0.2.15 facility all protocol 'udp' set system syslog host 10.0.2.15 port '122' set system syslog user paul facility local7 level 'err' set system syslog user vyos facility local6 level 'alert' set system syslog user vyos facility local7 level 'debug' """, ) playbook = dict(config=dict()) compare_cmds = ["delete system syslog"] playbook["state"] = "deleted" set_module_args(playbook) result = self.execute_module(changed=True) self.maxDiff = None self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) def test_vyos_logging_global_replaced(self): """ passing all commands as have and expecting [] commands """ self.execute_show_command.return_value = dedent( """\ set system syslog console facility all set system syslog console facility local7 level 'err' set system syslog console facility news level 'debug' set system syslog file xyz set system syslog file abc archive size '125' set system syslog file def archive file '2' set system syslog file def facility local6 level 'emerg' set system syslog file def facility local7 level 'emerg' set system syslog global archive file '2' set system syslog global archive size '111' set system syslog global facility cron level 'debug' set system syslog global facility local7 level 'debug' set system syslog global marker interval '111' set system syslog global preserve-fqdn set system syslog host 10.0.2.12 facility all protocol 'udp' set system syslog host 10.0.2.15 facility all level 'all' set system syslog host 10.0.2.15 facility all protocol 'udp' set system syslog host 10.0.2.15 port '122' set system syslog user paul facility local7 level 'err' set system syslog user vyos facility local6 level 'alert' set system syslog user vyos facility local7 level 'debug' """, ) playbook = dict( config=dict( console=dict(facilities=[dict(facility="local7", severity="emerg")]), files=[ dict( path="abc", archive=dict(file_num=2), facilities=[ dict(facility="local6", severity="err"), dict(facility="local7", severity="emerg"), ], ), ], ), ) compare_cmds = [ "delete system syslog console facility all", "delete system syslog console facility local7", "delete system syslog console facility news", "delete system syslog file def", "delete system syslog file xyz", "delete system syslog global facility cron", "delete system syslog global facility local7", "delete system syslog global archive file 2", "delete system syslog global archive size 111", "delete system syslog global marker", "delete system syslog global preserve-fqdn", "delete system syslog host 10.0.2.12", "delete system syslog host 10.0.2.15", "delete system syslog user paul", "delete system syslog user vyos", "set system syslog console facility local7 level emerg", "set system syslog file abc facility local6 level err", "set system syslog file abc facility local7 level emerg", "delete system syslog file abc archive size 125", "set system syslog file abc archive file 2", ] playbook["state"] = "replaced" set_module_args(playbook) result = self.execute_module(changed=True) self.maxDiff = None self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) def test_vyos_logging_global_replaced_idempotent(self): """ passing all commands as have and expecting [] commands """ self.execute_show_command.return_value = dedent( """\ set system syslog console facility local6 """, ) playbook = dict(config=dict(console=dict(facilities=[dict(facility="local6")]))) compare_cmds = [] playbook["state"] = "replaced" set_module_args(playbook) result = self.execute_module(changed=False) self.maxDiff = None self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) def test_vyos_logging_global_overridden(self): self.execute_show_command.return_value = dedent( """\ set system syslog console set system syslog global """, ) playbook = dict( config=dict( console=dict(facilities=[dict(facility="local7", severity="emerg")]), files=[ dict( path="abc", archive=dict(file_num=2), facilities=[ dict(facility="local6", severity="err"), dict(facility="local7", severity="emerg"), ], ), ], ), ) compare_cmds = [ "set system syslog console facility local7 level emerg", "set system syslog file abc facility local6 level err", "set system syslog file abc facility local7 level emerg", "set system syslog file abc archive file 2", ] playbook["state"] = "overridden" set_module_args(playbook) result = self.execute_module(changed=True) - print(result["commands"]) self.maxDiff = None self.assertEqual(sorted(result["commands"]), sorted(compare_cmds)) def test_vyos_logging_global_rendered(self): playbook = dict( config=dict( console=dict(facilities=[dict(facility="all")]), hosts=[ dict( hostname="10.0.2.16", facilities=[dict(facility="local6")], ), ], users=[ dict(username="vyos"), dict(username="paul", facilities=[dict(facility="local7")]), ], ), ) compare_cmds = [ "set system syslog console facility all", "set system syslog host 10.0.2.16 facility local6", "set system syslog user vyos", "set system syslog user paul facility local7", ] playbook["state"] = "rendered" set_module_args(playbook) result = self.execute_module() self.maxDiff = None self.assertEqual(sorted(result["rendered"]), sorted(compare_cmds)) def test_vyos_logging_global_parsed(self): set_module_args( dict( running_config=dedent( """\ set system syslog console facility all set system syslog file xyz """, ), state="parsed", ), ) parsed = dict( console=dict(facilities=[dict(facility="all")]), files=[dict(path="xyz")], ) result = self.execute_module(changed=False) self.maxDiff = None self.assertEqual(sorted(result["parsed"]), sorted(parsed)) def test_vyos_logging_global_gathered(self): self.execute_show_command.return_value = dedent( """\ set system syslog console facility all """, ) set_module_args(dict(state="gathered")) gathered = dict(console=dict(facilities=[dict(facility="all")])) result = self.execute_module(changed=False) self.maxDiff = None self.assertEqual(sorted(result["gathered"]), sorted(gathered)) diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py index 1d12a3cb..c7d69d0d 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py @@ -1,445 +1,459 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_ospf_interfaces from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosOspfInterfacesModule(TestVyosModule): module = vyos_ospf_interfaces def setUp(self): super(TestVyosOspfInterfacesModule, self).setUp() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data", ) self.execute_show_command = self.mock_execute_show_command.start() + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version" + ) + self.test_version = "1.2" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version" + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None def tearDown(self): super(TestVyosOspfInterfacesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + self.mock_facts_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): if filename is None: filename = "vyos_ospf_interfaces_config.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def sort_address_family(self, entry_list): for entry in entry_list: if entry.get("address_family"): entry["address_family"].sort(key=lambda i: i.get("afi")) def test_vyos_ospf_interfaces_merged_new_config(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="merged", ), ) commands = [ "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "set interfaces ethernet eth0 ipv6 ospfv3 instance-id 20", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_merged_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_existing_config_merged(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", cost=500), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", priority=100, ), dict(afi="ipv6", ifmtu=25), ], ), ], ), ) commands = [ "set interfaces ethernet eth0 ipv6 ospfv3 cost 500", "set interfaces ethernet eth1 ip ospf priority 100", "set interfaces ethernet eth1 ipv6 ospfv3 ifmtu 25", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="replaced", ), ) commands = [ "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33", "delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_overridden(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="overridden", ), ) commands = [ "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "delete interfaces ethernet eth1 ip ospf", "delete interfaces ethernet eth1 ipv6 ospfv3", "delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", "delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_overridden_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="overridden", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_deleted(self): set_module_args( dict( config=[ dict( name="eth0", ), ], state="deleted", ), ) commands = ["delete interfaces ethernet eth0 ipv6 ospfv3"] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_notpresent_deleted(self): set_module_args( dict( config=[ dict( name="eth3", ), ], state="deleted", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_rendered(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="rendered", ), ) commands = [ "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", "set interfaces ethernet eth0 ipv6 ospfv3 instance-id 20", "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", ] result = self.execute_module(changed=False) self.assertEqual(sorted(result["rendered"]), sorted(commands), result["rendered"]) def test_vyos_ospf_interfaces_parsed(self): commands = [ "set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345'", "set interfaces bonding bond2 ip ospf bandwidth '70'", "set interfaces bonding bond2 ip ospf transmit-delay '45'", "set interfaces bonding bond2 ipv6 ospfv3 'passive'", "set interfaces ethernet eth0 ip ospf cost '50'", "set interfaces ethernet eth0 ip ospf priority '26'", "set interfaces ethernet eth0 ipv6 ospfv3 instance-id '33'", "set interfaces ethernet eth0 ipv6 ospfv3 'mtu-ignore'", "set interfaces ethernet eth1 ip ospf network 'point-to-point'", "set interfaces ethernet eth1 ip ospf priority '26'", "set interfaces ethernet eth1 ip ospf transmit-delay '50'", "set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39'", ] parsed_str = "\n".join(commands) set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_list = [ { "address_family": [ { "afi": "ipv4", "authentication": { "md5_key": { "key": "1111111111232345", "key_id": 10, }, }, "bandwidth": 70, "transmit_delay": 45, }, {"afi": "ipv6", "passive": True}, ], "name": "bond2", }, { "address_family": [ {"afi": "ipv4", "cost": 50, "priority": 26}, {"afi": "ipv6", "instance": "33", "mtu_ignore": True}, ], "name": "eth0", }, { "address_family": [ { "afi": "ipv4", "network": "point-to-point", "priority": 26, "transmit_delay": 50, }, {"afi": "ipv6", "dead_interval": 39}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["parsed"]) given_list = self.sort_address_family(parsed_list) self.assertEqual(result_list, given_list) def test_vyos_ospf_interfaces_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module(changed=False, filename="vyos_ospf_interfaces_config.cfg") gathered_list = [ { "address_family": [{"afi": "ipv6", "instance": "33", "mtu_ignore": True}], "name": "eth0", }, { "address_family": [ {"afi": "ipv4", "cost": 100}, {"afi": "ipv6", "ifmtu": 33}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["gathered"]) given_list = self.sort_address_family(gathered_list) self.assertEqual(result_list, given_list) diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py similarity index 67% copy from tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py copy to tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py index 1d12a3cb..ef27860a 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py @@ -1,445 +1,511 @@ -# (c) 2016 Red Hat Inc. +# Spawned from test_vyos_ospf_interfaces (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function - __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_ospf_interfaces from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture -class TestVyosOspfInterfacesModule(TestVyosModule): +class TestVyosOspfInterfacesModule14(TestVyosModule): module = vyos_ospf_interfaces def setUp(self): - super(TestVyosOspfInterfacesModule, self).setUp() + super(TestVyosOspfInterfacesModule14, self).setUp() self.mock_get_resource_connection_config = patch( - "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection", + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection" ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_execute_show_command = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data", + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data" ) self.execute_show_command = self.mock_execute_show_command.start() + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version" + ) + self.test_version = "1.4" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version" + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None def tearDown(self): - super(TestVyosOspfInterfacesModule, self).tearDown() + super(TestVyosOspfInterfacesModule14, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): if filename is None: - filename = "vyos_ospf_interfaces_config.cfg" + filename = "vyos_ospf_interfaces_config_14.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def sort_address_family(self, entry_list): for entry in entry_list: if entry.get("address_family"): entry["address_family"].sort(key=lambda i: i.get("afi")) def test_vyos_ospf_interfaces_merged_new_config(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="merged", - ), + ) ) commands = [ - "set interfaces bonding bond2 ip ospf transmit-delay 9", - "set interfaces bonding bond2 ipv6 ospfv3 passive", - "set interfaces ethernet eth0 ip ospf cost 100", - "set interfaces ethernet eth0 ip ospf priority 55", - "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", - "set interfaces ethernet eth0 ipv6 ospfv3 instance-id 20", + "set protocols ospf interface bond2 transmit-delay 9", + "set protocols ospfv3 interface bond2 passive", + "set protocols ospf interface eth0 cost 100", + "set protocols ospf interface eth0 priority 55", + "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", + "set protocols ospfv3 interface eth0 instance-id 20", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_merged_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], - ), + ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_existing_config_merged(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", cost=500), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", priority=100, ), dict(afi="ipv6", ifmtu=25), ], ), ], - ), + ) ) commands = [ - "set interfaces ethernet eth0 ipv6 ospfv3 cost 500", - "set interfaces ethernet eth1 ip ospf priority 100", - "set interfaces ethernet eth1 ipv6 ospfv3 ifmtu 25", + "set protocols ospfv3 interface eth0 cost 500", + "set protocols ospf interface eth1 priority 100", + "set protocols ospfv3 interface eth1 ifmtu 25", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="replaced", - ), + ) + ) + commands = [ + "set protocols ospf interface bond2 transmit-delay 9", + "set protocols ospfv3 interface bond2 passive", + "set protocols ospf interface eth0 cost 100", + "set protocols ospf interface eth0 priority 55", + "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", + "delete protocols ospfv3 interface eth0 instance-id 33", + "delete protocols ospfv3 interface eth0 mtu-ignore", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_ospf_passive_interfaces_replaced(self): + set_module_args( + dict( + config=[ + dict( + name="eth0", + address_family=[ + dict( + afi="ipv4", + passive=True, + ), + ], + ), + dict( + name="eth1", + address_family=[ + dict( + afi="ipv4", + passive=True, + ), + dict( + afi="ipv6", + passive=True, + ), + ], + ), + dict( + name="bond2", + address_family=[ + dict( + afi="ipv4", + passive=True, + ), + dict(afi="ipv6", passive=True), + ], + ), + ], + state="replaced", + ) ) commands = [ - "set interfaces bonding bond2 ip ospf transmit-delay 9", - "set interfaces bonding bond2 ipv6 ospfv3 passive", - "set interfaces ethernet eth0 ip ospf cost 100", - "set interfaces ethernet eth0 ip ospf priority 55", - "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", - "delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33", - "delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", + "delete protocols ospf interface eth1 cost 100", + "delete protocols ospfv3 interface eth0 instance-id 33", + "delete protocols ospfv3 interface eth0 mtu-ignore", + "delete protocols ospfv3 interface eth1 ifmtu 33", + "set protocols ospf interface bond2 passive", + "set protocols ospfv3 interface bond2 passive", + "set protocols ospf interface eth0 passive", + "set protocols ospf interface eth1 passive", + "set protocols ospfv3 interface eth1 passive", ] + self.maxDiff = None self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="replaced", - ), + ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_overridden(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="overridden", - ), + ) ) commands = [ - "set interfaces bonding bond2 ip ospf transmit-delay 9", - "set interfaces bonding bond2 ipv6 ospfv3 passive", - "set interfaces ethernet eth0 ip ospf cost 100", - "set interfaces ethernet eth0 ip ospf priority 55", - "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", - "delete interfaces ethernet eth1 ip ospf", - "delete interfaces ethernet eth1 ipv6 ospfv3", - "delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", - "delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33", + "set protocols ospf interface bond2 transmit-delay 9", + "set protocols ospfv3 interface bond2 passive", + "set protocols ospf interface eth0 cost 100", + "set protocols ospf interface eth0 priority 55", + "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", + "delete protocols ospf interface eth1", + "delete protocols ospfv3 interface eth1", + "delete protocols ospfv3 interface eth0 mtu-ignore", + "delete protocols ospfv3 interface eth0 instance-id 33", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_overridden_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="overridden", - ), + ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_deleted(self): set_module_args( dict( config=[ dict( name="eth0", ), ], state="deleted", - ), + ) ) - commands = ["delete interfaces ethernet eth0 ipv6 ospfv3"] + commands = ["delete protocols ospfv3 interface eth0"] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_notpresent_deleted(self): set_module_args( dict( config=[ dict( name="eth3", ), ], state="deleted", - ), + ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_rendered(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="rendered", - ), + ) ) commands = [ - "set interfaces ethernet eth0 ip ospf cost 100", - "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", - "set interfaces ethernet eth0 ip ospf priority 55", - "set interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", - "set interfaces ethernet eth0 ipv6 ospfv3 instance-id 20", - "set interfaces bonding bond2 ip ospf transmit-delay 9", - "set interfaces bonding bond2 ipv6 ospfv3 passive", + "set protocols ospf interface eth0 cost 100", + "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", + "set protocols ospf interface eth0 priority 55", + "set protocols ospfv3 interface eth0 mtu-ignore", + "set protocols ospfv3 interface eth0 instance-id 20", + "set protocols ospf interface bond2 transmit-delay 9", + "set protocols ospfv3 interface bond2 passive", ] result = self.execute_module(changed=False) self.assertEqual(sorted(result["rendered"]), sorted(commands), result["rendered"]) def test_vyos_ospf_interfaces_parsed(self): commands = [ - "set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345'", - "set interfaces bonding bond2 ip ospf bandwidth '70'", - "set interfaces bonding bond2 ip ospf transmit-delay '45'", - "set interfaces bonding bond2 ipv6 ospfv3 'passive'", - "set interfaces ethernet eth0 ip ospf cost '50'", - "set interfaces ethernet eth0 ip ospf priority '26'", - "set interfaces ethernet eth0 ipv6 ospfv3 instance-id '33'", - "set interfaces ethernet eth0 ipv6 ospfv3 'mtu-ignore'", - "set interfaces ethernet eth1 ip ospf network 'point-to-point'", - "set interfaces ethernet eth1 ip ospf priority '26'", - "set interfaces ethernet eth1 ip ospf transmit-delay '50'", - "set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39'", + "set protocols ospf interface bond2 authentication md5 key-id 10 md5-key '1111111111232345'", + "set protocols ospf interface bond2 bandwidth '70'", + "set protocols ospf interface bond2 transmit-delay '45'", + "set protocols ospfv3 interface bond2 'passive'", + "set protocols ospf interface eth0 cost '50'", + "set protocols ospf interface eth0 priority '26'", + "set protocols ospfv3 interface eth0 instance-id '33'", + "set protocols ospfv3 interface eth0 'mtu-ignore'", + "set protocols ospf interface eth1 network 'point-to-point'", + "set protocols ospf interface eth1 priority '26'", + "set protocols ospf interface eth1 transmit-delay '50'", + "set protocols ospfv3 interface eth1 dead-interval '39'", ] parsed_str = "\n".join(commands) set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_list = [ { "address_family": [ { "afi": "ipv4", "authentication": { "md5_key": { "key": "1111111111232345", "key_id": 10, - }, + } }, "bandwidth": 70, "transmit_delay": 45, }, {"afi": "ipv6", "passive": True}, ], "name": "bond2", }, { "address_family": [ {"afi": "ipv4", "cost": 50, "priority": 26}, {"afi": "ipv6", "instance": "33", "mtu_ignore": True}, ], "name": "eth0", }, { "address_family": [ { "afi": "ipv4", "network": "point-to-point", "priority": 26, "transmit_delay": 50, }, {"afi": "ipv6", "dead_interval": 39}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["parsed"]) given_list = self.sort_address_family(parsed_list) self.assertEqual(result_list, given_list) def test_vyos_ospf_interfaces_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module(changed=False, filename="vyos_ospf_interfaces_config.cfg") gathered_list = [ { "address_family": [{"afi": "ipv6", "instance": "33", "mtu_ignore": True}], "name": "eth0", }, { "address_family": [ {"afi": "ipv4", "cost": 100}, {"afi": "ipv6", "ifmtu": 33}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["gathered"]) given_list = self.sort_address_family(gathered_list) self.assertEqual(result_list, given_list)