diff options
| author | omnom62 <omnom62@outlook.com> | 2026-10-05 20:56:05 +1000 |
|---|---|---|
| committer | omnom62 <omnom62@outlook.com> | 2026-10-05 20:56:05 +1000 |
| commit | 8ee802e41374942965b6b93cfb4534725ef45145 (patch) | |
| tree | ef3e5579bd767422caa6fe77cea6a7b5d2d8562c /roles/bridge_firewall | |
| parent | 748df2bc1d35fa285dd3fe46916e1230408778c7 (diff) | |
| download | vyos.blueprints-main.tar.gz vyos.blueprints-main.zip | |
T9393: new rolesmain
Diffstat (limited to 'roles/bridge_firewall')
| -rw-r--r-- | roles/bridge_firewall/defaults/main.yml | 4 | ||||
| -rw-r--r-- | roles/bridge_firewall/meta/argument_specs.yml | 163 | ||||
| -rw-r--r-- | roles/bridge_firewall/meta/main.yml | 11 | ||||
| -rw-r--r-- | roles/bridge_firewall/tasks/main.yml | 35 | ||||
| -rw-r--r-- | roles/bridge_firewall/tasks/verify.yml | 27 | ||||
| -rw-r--r-- | roles/bridge_firewall/templates/bridge_cli.j2 | 72 | ||||
| -rw-r--r-- | roles/bridge_firewall/templates/ipv4_rules.yml.j2 | 54 | ||||
| -rw-r--r-- | roles/bridge_firewall/vars/main.yml | 11 |
8 files changed, 377 insertions, 0 deletions
diff --git a/roles/bridge_firewall/defaults/main.yml b/roles/bridge_firewall/defaults/main.yml new file mode 100644 index 0000000..f656cb5 --- /dev/null +++ b/roles/bridge_firewall/defaults/main.yml @@ -0,0 +1,4 @@ +--- +bridge_firewall_state_rules: true +bridge_firewall_bridge_forward_default_action: drop +bridge_firewall_ipv4_forward_default_action: drop diff --git a/roles/bridge_firewall/meta/argument_specs.yml b/roles/bridge_firewall/meta/argument_specs.yml new file mode 100644 index 0000000..6d5493e --- /dev/null +++ b/roles/bridge_firewall/meta/argument_specs.yml @@ -0,0 +1,163 @@ +--- +argument_specs: + main: + short_description: Bridges with bridge and IPv4 firewall (docs blueprint "Bridge and firewall example") + description: + - Per bridge, creates the bridge and its members, an interface group + C(<bridge>-ifaces), bridge-firewall rulesets C(<bridge>-pre) (prerouting) + and C(<bridge>-fwd) (forward) with the jump rules that select them, and + optionally an IPv4 ruleset C(ip-<bridge>-fwd) for routed traffic and an + input rule for access to the router - the structure used on the docs page. + - Bridges, interface groups and the bridge firewall use + vyos.vyos.vyos_config, because vyos.vyos has no resource modules for + them yet. Member descriptions and the IPv4 firewall use resource modules. + options: + bridge_firewall_bridges: + type: list + elements: dict + required: true + description: Bridges, in order; their position sets the jump rule numbers (10, 20, ... and 110, 120, ...). + options: + name: + type: str + required: true + description: Bridge interface, e.g. C(br1). + description: + type: str + description: Bridge description. + addresses: + type: list + elements: str + default: [] + description: Bridge addresses; omit for a pure layer-2 bridge. + members: + type: list + elements: dict + required: true + description: Member interfaces. + options: + interface: + type: str + required: true + description: Member interface, e.g. C(eth3). + description: + type: str + description: Member interface description. + prerouting: + type: dict + description: Bridge ruleset C(<bridge>-pre), applied in the prerouting chain. + options: &bridge_ruleset + default_action: + type: str + choices: [accept, drop] + description: Ruleset default action (C(accept) for prerouting, C(drop) for forward by default). + rules: + type: list + elements: dict + default: [] + description: Bridge firewall rules. + options: + number: + type: int + required: true + description: Rule number. + action: + type: str + choices: [accept, drop] + default: accept + description: Rule action. + description: + type: str + description: Rule description. + ethernet_type: + type: str + description: Ethernet type, e.g. C(ipv4), C(ipv6), C(arp). + protocol: + type: str + description: IP protocol, e.g. C(udp). + source: + type: dict + description: Source match (C(address), C(port), C(mac_address)). + destination: + type: dict + description: Destination match (C(address), C(port), C(mac_address)). + inbound_interface: + type: str + description: Inbound bridge port, e.g. C(eth6). + state: + type: list + elements: str + default: [] + description: Connection states, e.g. C([new]). + log: + type: bool + default: false + description: Log matches. + forward: + type: dict + description: Bridge ruleset C(<bridge>-fwd), applied in the bridge forward chain. + options: *bridge_ruleset + ip_forward: + type: dict + description: IPv4 ruleset C(ip-<bridge>-fwd) for traffic routed out of the bridge. + options: + default_action: + type: str + choices: [accept, drop, reject] + default: drop + description: Ruleset default action. + rules: + type: list + elements: dict + default: [] + description: IPv4 rules. + options: + number: + type: int + required: true + description: Rule number. + action: + type: str + choices: [accept, drop, reject] + default: accept + description: Rule action. + description: + type: str + description: Rule description. + outbound_interface: + type: str + description: Outbound interface, e.g. the internet uplink. + outbound_bridge: + type: str + description: Another bridge from this list, matched via its interface group. + router_access: + type: str + choices: [accept, drop] + description: Accept or drop IPv4 traffic from this bridge to the router itself. + bridge_firewall_state_rules: + type: bool + default: true + description: Add the page's established/related and invalid rules to the forward and input chains. + bridge_firewall_bridge_forward_default_action: + type: str + choices: [accept, drop] + default: drop + description: Default action of the bridge forward chain. + bridge_firewall_ipv4_forward_default_action: + type: str + choices: [accept, drop, reject] + default: drop + description: Default action of the IPv4 forward chain. + vyos_blueprints_render_only: + type: bool + default: false + description: Collect commands into C(vyos_blueprints_rendered) instead of configuring. + verify: + short_description: Post-deployment checks for the bridge_firewall role + description: Run with C(tasks_from=verify). Checks bridge membership and that the bridge rulesets are active. + options: + bridge_firewall_bridges: + type: list + elements: dict + required: true + description: Same value as for C(main). diff --git a/roles/bridge_firewall/meta/main.yml b/roles/bridge_firewall/meta/main.yml new file mode 100644 index 0000000..4e556cb --- /dev/null +++ b/roles/bridge_firewall/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: VyOS maintainers and contributors + description: Bridges with bridge-layer and IPv4 firewall policies per bridge + license: GPL-3.0-or-later + min_ansible_version: "2.16" + platforms: + - name: GenericLinux + versions: [all] + galaxy_tags: [vyos, networking, bridge, firewall] +dependencies: [] diff --git a/roles/bridge_firewall/tasks/main.yml b/roles/bridge_firewall/tasks/main.yml new file mode 100644 index 0000000..d947258 --- /dev/null +++ b/roles/bridge_firewall/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: Set member interface descriptions + vyos.vyos.vyos_interfaces: + config: "{{ _bridge_firewall_member_descriptions }}" + state: "{{ _bridge_firewall_state }}" + register: _bridge_firewall_r_ifaces + when: _bridge_firewall_member_descriptions | length > 0 + +- name: Create bridges, interface groups and the bridge firewall + # No vyos.vyos resource module covers bridges, interface groups or the + # bridge firewall yet, so these go through vyos_config. + vyos.vyos.vyos_config: + lines: "{{ _bridge_firewall_cli_lines }}" + register: _bridge_firewall_r_cli + when: not (vyos_blueprints_render_only | default(false) | bool) + +- name: Configure the IPv4 input and forward filters + vyos.vyos.vyos_firewall_rules: + config: "{{ _bridge_firewall_ipv4 }}" + state: "{{ _bridge_firewall_state }}" + vars: + _bridge_firewall_ipv4: "{{ lookup('ansible.builtin.template', 'ipv4_rules.yml.j2') | from_yaml }}" + register: _bridge_firewall_r_ipv4 + when: >- + bridge_firewall_bridges | selectattr('ip_forward', 'defined') | list | length > 0 + or bridge_firewall_bridges | selectattr('router_access', 'defined') | list | length > 0 + +- name: Collect rendered commands # noqa: var-naming[no-role-prefix] - shared across roles by design + ansible.builtin.set_fact: + vyos_blueprints_rendered: >- + {{ vyos_blueprints_rendered | default([]) + + (_bridge_firewall_r_ifaces.rendered | default([])) + + _bridge_firewall_cli_lines + + (_bridge_firewall_r_ipv4.rendered | default([])) }} + when: vyos_blueprints_render_only | default(false) | bool diff --git a/roles/bridge_firewall/tasks/verify.yml b/roles/bridge_firewall/tasks/verify.yml new file mode 100644 index 0000000..295b8af --- /dev/null +++ b/roles/bridge_firewall/tasks/verify.yml @@ -0,0 +1,27 @@ +--- +- name: Read bridges and bridge firewall + vyos.vyos.vyos_command: + commands: + - show configuration commands + - show firewall bridge + register: _bridge_firewall_v + +- name: Every member interface is in its bridge + ansible.builtin.assert: + that: >- + _bridge_firewall_v.stdout[0] is search('set interfaces bridge ' ~ (item.0.name | regex_escape) + ~ ' member interface ' ~ (item.1.interface | regex_escape) ~ '\b') + fail_msg: "{{ item.1.interface }} is not a member of {{ item.0.name }}" + quiet: true + loop: "{{ bridge_firewall_bridges | subelements('members') }}" + loop_control: + label: "{{ item.0.name }} {{ item.1.interface }}" + +- name: Every bridge ruleset is active + ansible.builtin.assert: + that: _bridge_firewall_v.stdout[1] is search('name ' ~ item) + fail_msg: "bridge ruleset {{ item }} is not active" + quiet: true + loop: >- + {{ (bridge_firewall_bridges | selectattr('prerouting', 'defined') | map(attribute='name') | map('regex_replace', '$', '-pre') | list) + + (bridge_firewall_bridges | selectattr('forward', 'defined') | map(attribute='name') | map('regex_replace', '$', '-fwd') | list) }} diff --git a/roles/bridge_firewall/templates/bridge_cli.j2 b/roles/bridge_firewall/templates/bridge_cli.j2 new file mode 100644 index 0000000..18cb1f3 --- /dev/null +++ b/roles/bridge_firewall/templates/bridge_cli.j2 @@ -0,0 +1,72 @@ +{#- Bridges, interface groups and the bridge firewall: no vyos.vyos resource + module covers these yet. Rule numbers for the jump rules follow the page. -#} +{%- macro bridge_rule(prefix, r) -%} +{{ prefix }} rule {{ r.number }} action '{{ r.action | default('accept') }}' +{% if r.description is defined %} +{{ prefix }} rule {{ r.number }} description '{{ r.description }}' +{% endif %} +{% for k in ['ethernet_type', 'protocol'] if r[k] is defined %} +{{ prefix }} rule {{ r.number }} {{ k | replace('_', '-') }} '{{ r[k] }}' +{% endfor %} +{% for side in ['source', 'destination'] if r[side] is defined %} +{% for k in ['address', 'port', 'mac_address'] if r[side][k] is defined %} +{{ prefix }} rule {{ r.number }} {{ side }} {{ k | replace('_', '-') }} '{{ r[side][k] }}' +{% endfor %} +{% endfor %} +{% if r.inbound_interface is defined %} +{{ prefix }} rule {{ r.number }} inbound-interface name '{{ r.inbound_interface }}' +{% endif %} +{% for s in r.state | default([]) %} +{{ prefix }} rule {{ r.number }} state '{{ s }}' +{% endfor %} +{% if r.log | default(false) | bool %} +{{ prefix }} rule {{ r.number }} log +{% endif %} +{%- endmacro -%} +{% for b in bridge_firewall_bridges %} +{% set i = loop.index %} +{% set grp = b.name ~ '-ifaces' %} +{% if b.description is defined %} +set interfaces bridge {{ b.name }} description '{{ b.description }}' +{% endif %} +{% for a in b.addresses | default([]) %} +set interfaces bridge {{ b.name }} address '{{ a }}' +{% endfor %} +{% for m in b.members %} +set interfaces bridge {{ b.name }} member interface {{ m.interface }} +{% endfor %} +set firewall group interface-group {{ grp }} interface '{{ b.name }}' +{% for m in b.members %} +set firewall group interface-group {{ grp }} interface '{{ m.interface }}' +{% endfor %} +{% if b.prerouting is defined %} +set firewall bridge prerouting filter rule {{ i * 10 }} action 'jump' +set firewall bridge prerouting filter rule {{ i * 10 }} description '{{ b.name }} traffic' +set firewall bridge prerouting filter rule {{ i * 10 }} inbound-interface group '{{ grp }}' +set firewall bridge prerouting filter rule {{ i * 10 }} jump-target '{{ b.name }}-pre' +set firewall bridge name {{ b.name }}-pre default-action '{{ b.prerouting.default_action | default('accept') }}' +{% for r in b.prerouting.rules | default([]) %} +{{ bridge_rule('set firewall bridge name ' ~ b.name ~ '-pre', r) }} +{% endfor %} +{% endif %} +{% if b.forward is defined %} +set firewall bridge forward filter rule {{ 100 + i * 10 }} action 'jump' +set firewall bridge forward filter rule {{ 100 + i * 10 }} description '{{ b.name }} traffic' +set firewall bridge forward filter rule {{ 100 + i * 10 }} inbound-interface group '{{ grp }}' +set firewall bridge forward filter rule {{ 100 + i * 10 }} jump-target '{{ b.name }}-fwd' +set firewall bridge name {{ b.name }}-fwd default-action '{{ b.forward.default_action | default('drop') }}' +{% for r in b.forward.rules | default([]) %} +{{ bridge_rule('set firewall bridge name ' ~ b.name ~ '-fwd', r) }} +{% endfor %} +{% endif %} +{% endfor %} +{% if bridge_firewall_bridges | selectattr('forward', 'defined') | list %} +{% if bridge_firewall_state_rules | bool %} +set firewall bridge forward filter rule 5 action 'accept' +set firewall bridge forward filter rule 5 state 'established' +set firewall bridge forward filter rule 5 state 'related' +set firewall bridge forward filter rule 10 action 'drop' +set firewall bridge forward filter rule 10 state 'invalid' +{% endif %} +set firewall bridge forward filter default-action '{{ bridge_firewall_bridge_forward_default_action }}' +{% endif %} diff --git a/roles/bridge_firewall/templates/ipv4_rules.yml.j2 b/roles/bridge_firewall/templates/ipv4_rules.yml.j2 new file mode 100644 index 0000000..dec5520 --- /dev/null +++ b/roles/bridge_firewall/templates/ipv4_rules.yml.j2 @@ -0,0 +1,54 @@ +{#- IPv4 firewall for routed traffic and router access, via vyos_firewall_rules. -#} +{% set routed = bridge_firewall_bridges | selectattr('ip_forward', 'defined') | list %} +{% set access = bridge_firewall_bridges | selectattr('router_access', 'defined') | list %} +- afi: ipv4 + rule_sets: +{% if access %} + - filter: input + rules: +{% if bridge_firewall_state_rules | bool %} + - {number: 10, action: accept, state: {established: true, related: true}} + - {number: 20, action: drop, state: {invalid: true}} +{% endif %} +{% for b in access %} + - number: {{ 100 + loop.index * 10 }} + action: {{ 'accept' if b.router_access == 'accept' else 'drop' }} + description: {{ ((b.router_access == 'accept') | ternary('Accept access from ', 'Deny access from ') ~ b.name) | to_json }} + inbound_interface: {group: {{ (b.name ~ '-ifaces') | to_json }}} +{% endfor %} +{% endif %} +{% if routed %} + - filter: forward + default_action: {{ bridge_firewall_ipv4_forward_default_action }} + rules: +{% if bridge_firewall_state_rules | bool %} + - {number: 5, action: accept, state: {established: true, related: true}} + - {number: 10, action: drop, state: {invalid: true}} +{% endif %} +{% for b in routed %} + - number: {{ 100 + loop.index * 10 }} + action: jump + description: {{ (b.name ~ ' traffic') | to_json }} + inbound_interface: {group: {{ (b.name ~ '-ifaces') | to_json }}} + jump_target: {{ ('ip-' ~ b.name ~ '-fwd') | to_json }} +{% endfor %} +{% for b in routed %} + - name: {{ ('ip-' ~ b.name ~ '-fwd') | to_json }} + default_action: {{ b.ip_forward.default_action | default('drop') }} +{% if b.ip_forward.rules | default([]) %} + rules: +{% for r in b.ip_forward.rules %} + - number: {{ r.number | int }} + action: {{ r.action | default('accept') }} +{% if r.description is defined %} + description: {{ r.description | to_json }} +{% endif %} +{% if r.outbound_interface is defined %} + outbound_interface: {name: {{ r.outbound_interface | to_json }}} +{% elif r.outbound_bridge is defined %} + outbound_interface: {group: {{ (r.outbound_bridge ~ '-ifaces') | to_json }}} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/roles/bridge_firewall/vars/main.yml b/roles/bridge_firewall/vars/main.yml new file mode 100644 index 0000000..7299574 --- /dev/null +++ b/roles/bridge_firewall/vars/main.yml @@ -0,0 +1,11 @@ +--- +_bridge_firewall_state: "{{ 'rendered' if (vyos_blueprints_render_only | default(false) | bool) else 'merged' }}" +_bridge_firewall_cli_lines: "{{ (lookup('ansible.builtin.template', 'bridge_cli.j2') or '').splitlines() | map('trim') | select | list }}" +_bridge_firewall_member_descriptions: >- + {%- set out = [] -%} + {%- for b in bridge_firewall_bridges -%} + {%- for m in b.members if m.description is defined -%} + {%- set _ = out.append({'name': m.interface, 'description': m.description}) -%} + {%- endfor -%} + {%- endfor -%} + {{ out }} |
