Source NAT

STATE: unstable

TESTS: Playbook

API Docs: Core - Firewall

Service Docs: Outbound NAT

Prerequisites

You need to install the following plugin as OPNSense has no core-api for managing its firewall rules:

os-firewall

You can also install it using the ansibleguy.opnsense.package module.

Limitations

This plugin has some limitations you need to know of:

  • ports don’t support aliases

  • each of these parameters only takes ONE value per rule:

    • port

    • protocol (or ‘any’; ‘TCP/UDP’ is NOT valid)

    • ip-protocol (IPv4/IPv6)

  • the ruleset managed by this plugin is SEPARATE from the default WEB-UI rules (Firewall - NAT - Outbound) - combined usage might bring complications

  • interfaces must be provided as used in the network config (p.e. ‘opt1’ instead of ‘DMZ’)

    • per example see menu: ‘Interface - Assignments - Interface ID (in brackets)’

    • this brings problems if the interface-names are not the same on both nodes when using HA-setups

Info

Savepoint

You can prevent lockout-situations using the savepoint systems:

Web-UI

These rules are shown in the separate WEB-UI table.

Menu: ‘Firewall - Automation - Source NAT’

Definition

Module alias: ansibleguy.opnsense.snat

Definition

Parameter

Type

Required

Default

Aliases

Comment

match_fields

list

true

-

-

Fields that are used to match configured rules with the running config - if any of those fields are changed, the module will think it’s a new rule. At least one of: ‘sequence’, ‘action’, ‘interface’, ‘direction’, ‘ip_protocol’, ‘protocol’, ‘source_invert’, ‘source_net’, ‘source_port’, ‘destination_invert’, ‘destination_net’, ‘destination_port’, ‘gateway’, ‘description’, ‘uuid’

sequence

int

false

1

seq

Sequence for rule processing, Integer between 1 and 1000000

interface

string

false for deletion, else true

-

i, int

The interface to match this rule on

ip_protocol

string

false

‘inet’

ipp, ip_proto

IP protocol to match. One of: ‘inet’, ‘inet6’ (IPv4 = ‘inet’, IPv6 = ‘inet6’)

protocol

string

false

‘any’

p, proto

Protocol like ‘TCP’, ‘UDP’, ‘ICMP’ and so on. For options see the WEB-UI. ‘TCP/UDP’ is NOT valid!

source_invert

boolean

false

false

si, src_inv, src_not

Inverted matching of the source

source_net

string

false

‘any’

s, src, source

Host, network, alias or ‘any’

source_port

string

false

-

sp, src_port

Leave empty to allow all, alias not supported

destination_invert

boolean

false

false

di, dest_inv, dest_not

Inverted matching of the destination

destination_net

string

false

‘any’

d, dest, destination

Host, network, alias or ‘any’

destination_port

string

false

-

dp, dest_port

Leave empty to allow all, alias not supported

target

string

false for deletion, else true

-

tgt, t

NAT translation target - Packets matching this rule will be mapped to the IP address given here. Host, network or alias

target_port

string

false

-

np, nat_port

log

boolean

false

true

l

If rule matches should be shown in the firewall logs

description

string

false

-

desc

Description for the rule

state

string

false

‘present’

st

State of the rule. One of: ‘present’, ‘absent’

enabled

boolean

false

true

en

If the rule should be en- or disabled

uuid

string

false

-

-

Optionally you can supply the uuid of an existing rule

reload

boolean

false

true

apply

If the running config should be reloaded on change - this may take some time. For mass-managing items you might want to reload it ‘manually’ after all changes are done => using the ansibleguy.opnsense.reload module.

For basic parameters see: Basic

Usage

First you will have to know about rule-matching.

The module somehow needs to link the configured and existing rules to manage them.

You need to set how this matching is done by setting the ‘match_fields’ parameter!

It is recommended to use/set unique identifiers like ‘description’ to make sure rules can be matched without overlapping.

You could also use the UUID of existing rules as ID - but you would have to pull (list) and configure those ‘manually’.

Examples

- hosts: localhost
  gather_facts: no
  module_defaults:
    group/ansibleguy.opnsense.all:
      firewall: 'opnsense.template.ansibleguy.net'
      api_credential_file: '/home/guy/.secret/opn.key'

    ansibleguy.opnsense.source_nat:
      match_fields: ['description']

    ansibleguy.opnsense.list:
      target: 'source_nat'

  tasks:
    - name: Example
      ansibleguy.opnsense.source_nat:
        description: 'example'
        match_fields: ['description']
        target: '192.168.0.1'
        interface: 'opt1'
        # sequence: 1
        # ip_protocol: 'inet'
        # protocol: 'any'
        # source_invert: false
        # source_net: 'any'
        # source_port: 'any'
        # destination_invert: false
        # destination_net: 'any'
        # destination_port: 'any'
        # destination_port: 'any'
        # target_port: none
        # no_nat: false
        # log: true
        # enabled: true
        # debug: false
        # state: 'present'
        # reload: true

    - name: Adding rule
      ansibleguy.opnsense.source_nat:
        description: 'test1'
        source: '192.168.0.0/24'
        destination: '10.0.0.0/24'
        target: '10.0.0.1'
        interface: 'opt1'
        # match_fields: ['description']

    - name: Disabling rule
      ansibleguy.opnsense.source_nat:
        description: 'test1'
        source: '192.168.0.0/24'
        destination: '10.0.0.0/24'
        target: '10.0.0.1'
        interface: 'opt1'
        enabled: false
        # match_fields: ['description']

    - name: Listing
      ansibleguy.opnsense.list:
      #  target: 'source_nat'
      register: existing_entries

    - name: Printing peers
      ansible.builtin.debug:
        var: existing_entries.data

    - name: Removing rule
      ansibleguy.opnsense.source_nat:
        description: 'test1'
        state: 'absent'
        # match_fields: ['description']