diff --git a/docs/dictionary/en-custom.txt b/docs/dictionary/en-custom.txt index 3211585d50..d7acf6a9b1 100644 --- a/docs/dictionary/en-custom.txt +++ b/docs/dictionary/en-custom.txt @@ -207,6 +207,7 @@ ipam ipi ipmi ips +iptables ipv iscsi itldwuw diff --git a/roles/masquerade_external/README.md b/roles/masquerade_external/README.md new file mode 100644 index 0000000000..787bd67365 --- /dev/null +++ b/roles/masquerade_external/README.md @@ -0,0 +1,50 @@ +# masquerade_external +A role to configure masquerading of traffic going out the default interface, +based on the source address. + +This is handy when libvirt networks need to have routing to different libvirt +networks on the host enabled. In `nat` mode libvirt inserts firewall rules that +prevent this. Instead of using `nat` mode for libvirt networks, the `routed` or +`open` mode can be used instead, and this role can set up the masquerading +rules so that the VMs on these networks can still reach external networks. + +Example rules inserted by this role: +```bash +Chain POSTROUTING (policy ACCEPT 118 packets, 13329 bytes) + pkts bytes target prot opt in out source destination + 0 0 CIFMW-PRT all -- any any anywhere anywhere + +Chain CIFMW-PRT (1 references) + pkts bytes target prot opt in out source destination + 0 0 MASQUERADE all -- any eth0 172.17.0.0/24 anywhere + 0 0 MASQUERADE all -- any eth0 172.16.0.0/24 anywhere +``` + +## Privilege escalation +Requires privileged escalation to manipulate iptables firewall. + +## Parameters +* `cifmw_masquerade_external_source_ranges`: (List) List of source IP or ip networks in CIDR format. Defaults to: `[]` +* `cifmw_masquerade_external_post_routing_chain_name`: (String) Name of the iptables chain +* `cifmw_masquerade_external_out_interface`: (String) Device name for the outgoing interface. Defaults to: `{{ hostvars[inventory_hostname].ansible_default_ipv4.interface }}` + +## Examples + +### Add masquerading rules for sources `172.16.0.0/24` and `172.17.0.0/24` +```yaml +- name: Include the role with var + vars: + cifmw_masquerade_external_source_ranges: + - '172.16.0.0/24' + - '172.17.0.0/24' + ansible.builtin.include_role: + name: masquerade_external +``` + +### Cleanup masquerading rules and chain +```yaml +- name: Include the role with var + ansible.builtin.include_role: + name: masquerade_external + tasks_from: cleanup.yml +``` diff --git a/roles/masquerade_external/defaults/main.yml b/roles/masquerade_external/defaults/main.yml new file mode 100644 index 0000000000..61b4c1b125 --- /dev/null +++ b/roles/masquerade_external/defaults/main.yml @@ -0,0 +1,23 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +# All variables intended for modification should be placed in this file. +# All variables within this role should have a prefix of "cifmw_masquerade_external" + +cifmw_masquerade_external_source_ranges: [] +cifmw_masquerade_external_post_routing_chain_name: CIFMW-PRT +cifmw_masquerade_external_out_interface: "{{ hostvars[inventory_hostname].ansible_default_ipv4.interface }}" diff --git a/roles/masquerade_external/meta/main.yml b/roles/masquerade_external/meta/main.yml new file mode 100644 index 0000000000..4a146d2798 --- /dev/null +++ b/roles/masquerade_external/meta/main.yml @@ -0,0 +1,41 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +galaxy_info: + author: CI Framework + description: CI Framework Role -- masquerade_external + company: Red Hat + license: Apache-2.0 + min_ansible_version: 2.14 + namespace: cifmw + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + platforms: + - name: CentOS + versions: + - 9 + + galaxy_tags: + - cifmw + +# List your role dependencies here, one per line. Be sure to remove the '[]' above, +# if you add dependencies to this list. +dependencies: [] diff --git a/roles/masquerade_external/molecule/default/converge.yml b/roles/masquerade_external/molecule/default/converge.yml new file mode 100644 index 0000000000..30ab60831a --- /dev/null +++ b/roles/masquerade_external/molecule/default/converge.yml @@ -0,0 +1,70 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Converge + hosts: instance + vars: + cifmw_masquerade_external_source_ranges: + - '172.16.0.0/24' + - '172.17.0.0/24' + tasks: + - name: Insert rules + ansible.builtin.include_role: + name: masquerade_external + + - name: Assert rules are present + block: + - name: List POSTROUTING chain in nat table + become: true + register: _out + ansible.builtin.command: 'iptables -t nat -L POSTROUTING -v' + - name: Assert CIFMW-PRT in POSTROUTING chain + vars: + ref: '\s*CIFMW-PRT\s*all\s*--\s*any\s*any\s*anywhere\s*anywhere' + ansible.builtin.assert: + that: _out.stdout is regex(ref) + - name: List CIFMW-PRT chain in nat table + become: true + register: _out + ansible.builtin.command: 'iptables -t nat -L CIFMW-PRT -v' + - name: Assert MASQUERADE rules in CIFMW-PRT chain + ansible.builtin.assert: + that: _out.stdout is regex(item) + loop: + - '\s*MASQUERADE\s*all\s*--\s*any\s*.*\s*172.16.0.0/24\s*anywhere' + - '\s*MASQUERADE\s*all\s*--\s*any\s*.*\s*172.17.0.0/24\s*anywhere' + + - name: Cleanup rules + ansible.builtin.include_role: + name: masquerade_external + tasks_from: cleanup.yml + + - name: Assert rules where removed + block: + - name: List POSTROUTING chain in nat table + become: true + register: _out + ansible.builtin.command: 'iptables -t nat -L POSTROUTING' + - name: Assert CIFMW-PRT is not in POSTROUTING chain + ansible.builtin.assert: + that: '"CIFMW-PRT" not in _out.stdout' + - name: List CIFMW-PRT chain in nat table - should fail with No chain ... + become: true + register: _out + ansible.builtin.command: 'iptables -t nat -L CIFMW-PRT -v' + failed_when: + - '"No chain/target/match by that name." not in _out.stderr' diff --git a/roles/masquerade_external/molecule/default/molecule.yml b/roles/masquerade_external/molecule/default/molecule.yml new file mode 100644 index 0000000000..fda947cafe --- /dev/null +++ b/roles/masquerade_external/molecule/default/molecule.yml @@ -0,0 +1,11 @@ +--- +# Mainly used to override the defaults set in .config/molecule/ +# By default, it uses the "config_podman.yml" - in CI, it will use +# "config_local.yml". +log: true + +provisioner: + name: ansible + log: true + env: + ANSIBLE_STDOUT_CALLBACK: yaml diff --git a/roles/masquerade_external/molecule/default/prepare.yml b/roles/masquerade_external/molecule/default/prepare.yml new file mode 100644 index 0000000000..d3594acc41 --- /dev/null +++ b/roles/masquerade_external/molecule/default/prepare.yml @@ -0,0 +1,21 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Prepare + hosts: all + roles: + - role: test_deps diff --git a/roles/masquerade_external/tasks/cleanup.yml b/roles/masquerade_external/tasks/cleanup.yml new file mode 100644 index 0000000000..b31a6da075 --- /dev/null +++ b/roles/masquerade_external/tasks/cleanup.yml @@ -0,0 +1,42 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +- name: Ensure cifmw post routing chain in POSTROUTING is removed + become: true + ansible.builtin.iptables: + state: absent + table: nat + chain: POSTROUTING + jump: "{{ cifmw_masquerade_external_post_routing_chain_name }}" + +- name: Ensure cifmw post routing chain is flushed + become: true + register: _flush + ansible.builtin.iptables: + table: nat + chain: "{{ cifmw_masquerade_external_post_routing_chain_name }}" + flush: true + failed_when: + - '_flush.rc is defined and _flush.rc != 0' + - '"No chain/target/match by that name." not in _flush.stderr' + +- name: Ensure cifmw post routing chain is removed + become: true + ansible.builtin.iptables: + state: absent + table: nat + chain_management: true + chain: "{{ cifmw_masquerade_external_post_routing_chain_name }}" diff --git a/roles/masquerade_external/tasks/main.yml b/roles/masquerade_external/tasks/main.yml new file mode 100644 index 0000000000..99f4ebe338 --- /dev/null +++ b/roles/masquerade_external/tasks/main.yml @@ -0,0 +1,37 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +- name: Ensure masquerading rules in cifmw post routing chain + become: true + ansible.builtin.iptables: + state: present + action: insert + table: nat + chain_management: true + chain: "{{ cifmw_masquerade_external_post_routing_chain_name }}" + source: "{{ item }}" + out_interface: "{{ cifmw_masquerade_external_out_interface }}" + jump: "MASQUERADE" + loop: "{{ cifmw_masquerade_external_source_ranges }}" + +- name: Ensure cifmw post routing chain in POSTROUTING + become: true + ansible.builtin.iptables: + state: present + action: insert + table: nat + chain: POSTROUTING + jump: "{{ cifmw_masquerade_external_post_routing_chain_name }}" diff --git a/roles/reproducer/tasks/cleanup.yml b/roles/reproducer/tasks/cleanup.yml index fe8ebb0c03..83d677848b 100644 --- a/roles/reproducer/tasks/cleanup.yml +++ b/roles/reproducer/tasks/cleanup.yml @@ -35,3 +35,8 @@ - name: Ensure handlers are flushed ansible.builtin.meta: flush_handlers + +- name: Clean up masquerading rules + ansible.builtin.import_role: + name: masquerade_external + tasks_from: cleanup.yml diff --git a/roles/reproducer/tasks/main.yml b/roles/reproducer/tasks/main.yml index 68ef68c1aa..310b7a7507 100644 --- a/roles/reproducer/tasks/main.yml +++ b/roles/reproducer/tasks/main.yml @@ -130,6 +130,18 @@ - bootstrap_env - bootstrap_layout +- name: Masquerade libvirt networks + when: + - cifmw_use_libvirt | default(false) | bool + - cifmw_masquerade_networks | default(false) | bool + - cifmw_masquerade_external_source_ranges is defined + - cifmw_masquerade_external_source_ranges | length > 0 + tags: + - bootstrap_layout + - bootstrap_env + ansible.builtin.include_role: + name: masquerade_external + - name: Bootstrap nat64 if needed when: - cifmw_use_libvirt | default(false) | bool diff --git a/zuul.d/molecule.yaml b/zuul.d/molecule.yaml index 7cc043b6f9..596f422b46 100644 --- a/zuul.d/molecule.yaml +++ b/zuul.d/molecule.yaml @@ -465,6 +465,17 @@ parent: cifmw-molecule-base vars: TEST_RUN: manage_secrets +- job: + files: + - ^common-requirements.txt + - ^test-requirements.txt + - ^roles/masquerade_external/(?!meta|README).* + - ^ci/playbooks/molecule.* + - ^.config/molecule/.* + name: cifmw-molecule-masquerade_external + parent: cifmw-molecule-base + vars: + TEST_RUN: masquerade_external - job: files: - ^common-requirements.txt diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml index 1b51102754..f146e250df 100644 --- a/zuul.d/projects.yaml +++ b/zuul.d/projects.yaml @@ -54,6 +54,7 @@ - cifmw-molecule-kustomize_deploy - cifmw-molecule-libvirt_manager - cifmw-molecule-manage_secrets + - cifmw-molecule-masquerade_external - cifmw-molecule-mirror_registry - cifmw-molecule-nat64_appliance - cifmw-molecule-networking_mapper