mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 20:43:02 +02:00
test: new test, zone migration, custom service, and IPv6
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -16,3 +16,7 @@ include::wan-dmz-lan/Readme.adoc[]
|
||||
<<<
|
||||
|
||||
include::ipv6-lan-wan/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::ipv6-zone-migration/Readme.adoc[]
|
||||
|
||||
@@ -10,3 +10,6 @@
|
||||
|
||||
- name: IPv6 LAN-WAN Firewall
|
||||
case: ipv6-lan-wan/test.py
|
||||
|
||||
- name: IPv6 Zone Migration with Custom Services
|
||||
case: ipv6-zone-migration/test.py
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -0,0 +1,31 @@
|
||||
=== IPv6 Zone Migration with Custom Services
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/infix_firewall/ipv6-zone-migration]
|
||||
|
||||
==== Description
|
||||
|
||||
This test verifies that firewall rules work consistently across IPv4/IPv6
|
||||
protocols and that interfaces can be moved between zones without breaking
|
||||
active connections.
|
||||
|
||||
- Requires DUT with at least 2 data interfaces supporting IPv6
|
||||
- Test host must support dual-stack IPv4/IPv6 configuration
|
||||
- Custom service ports (8080/tcp) should be available for testing
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[IPv6 Zone Migration with Custom Services topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target
|
||||
. Configure dual-stack interfaces and initial firewall
|
||||
. Verify initial zone configuration and custom service
|
||||
. Verify IPv4/IPv6 connectivity and custom service restrictions
|
||||
. Verify IPv6 custom service functionality
|
||||
. Perform dynamic zone migration
|
||||
. Verify connectivity after zone migration
|
||||
. Verify custom service from migrated interface
|
||||
. Verify operational state reflects zone changes
|
||||
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
"""IPv6 Zone Migration with Custom Service
|
||||
|
||||
This test verifies that firewall rules work consistently across IPv4/IPv6
|
||||
protocols and that interfaces can be moved between zones without breaking
|
||||
active connections.
|
||||
|
||||
- Requires DUT with at least 2 data interfaces supporting IPv6
|
||||
- Test host must support dual-stack IPv4/IPv6 configuration
|
||||
- Custom service ports (8080/tcp) should be available for testing
|
||||
"""
|
||||
|
||||
import time
|
||||
import infamy
|
||||
from infamy.util import until
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target"):
|
||||
env = infamy.Env()
|
||||
target = env.attach("target", "mgmt")
|
||||
_, data1_if = env.ltop.xlate("target", "data1")
|
||||
_, data2_if = env.ltop.xlate("target", "data2")
|
||||
_, mgmt_if = env.ltop.xlate("target", "mgmt")
|
||||
_, host_data1 = env.ltop.xlate("host", "data1")
|
||||
_, host_data2 = env.ltop.xlate("host", "data2")
|
||||
|
||||
# IPv4 addressing
|
||||
DATA1_NET_V4 = "10.1.1.0/24"
|
||||
DATA1_TARGET_V4 = "10.1.1.1"
|
||||
DATA1_HOST_V4 = "10.1.1.100"
|
||||
|
||||
DATA2_NET_V4 = "10.2.2.0/24"
|
||||
DATA2_TARGET_V4 = "10.2.2.1"
|
||||
DATA2_HOST_V4 = "10.2.2.100"
|
||||
|
||||
# IPv6 addressing
|
||||
DATA1_NET_V6 = "fd01:1:1::/64"
|
||||
DATA1_TARGET_V6 = "fd01:1:1::1"
|
||||
DATA1_HOST_V6 = "fd01:1:1::100"
|
||||
|
||||
DATA2_NET_V6 = "fd02:2:2::/64"
|
||||
DATA2_TARGET_V6 = "fd02:2:2::1"
|
||||
DATA2_HOST_V6 = "fd02:2:2::100"
|
||||
|
||||
# Custom service port
|
||||
CUSTOM_PORT = 8080
|
||||
|
||||
with test.step("Configure dual-stack interfaces and initial firewall"):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": data1_if,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": DATA1_TARGET_V4,
|
||||
"prefix-length": 24
|
||||
}]
|
||||
},
|
||||
"ipv6": {
|
||||
"enabled": True,
|
||||
"address": [{
|
||||
"ip": DATA1_TARGET_V6,
|
||||
"prefix-length": 64
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
"name": data2_if,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": DATA2_TARGET_V4,
|
||||
"prefix-length": 24
|
||||
}]
|
||||
},
|
||||
"ipv6": {
|
||||
"enabled": True,
|
||||
"address": [{
|
||||
"ip": DATA2_TARGET_V6,
|
||||
"prefix-length": 64
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
"infix-firewall": {
|
||||
"firewall": {
|
||||
"default": "untrusted",
|
||||
"logging": "all",
|
||||
"service": [{
|
||||
"name": "myapp",
|
||||
"port": [{
|
||||
"lower": CUSTOM_PORT,
|
||||
"proto": "tcp"
|
||||
}]
|
||||
}],
|
||||
"zone": [{
|
||||
"name": "mgmt",
|
||||
"description": "Management network",
|
||||
"action": "accept",
|
||||
"interface": [mgmt_if],
|
||||
"service": ["ssh", "netconf", "restconf"]
|
||||
}, {
|
||||
"name": "untrusted",
|
||||
"description": "Untrusted zone",
|
||||
"action": "accept",
|
||||
"interface": [data1_if]
|
||||
}, {
|
||||
"name": "trusted",
|
||||
"description": "Trusted zone",
|
||||
"action": "accept",
|
||||
"interface": [data2_if],
|
||||
"service": ["ssh", "myapp"]
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
# Wait for configuration to be activated
|
||||
infamy.Firewall.wait_for_operational(target, {
|
||||
"untrusted": {"action": "accept"},
|
||||
"trusted": {"action": "accept"},
|
||||
"mgmt": {"action": "accept"}
|
||||
})
|
||||
|
||||
with test.step("Verify initial zone configuration and custom service"):
|
||||
# Verify operational state matches expected configuration
|
||||
data = target.get_data("/infix-firewall:firewall")
|
||||
fw = data["firewall"]
|
||||
|
||||
assert fw["default"] == "untrusted"
|
||||
|
||||
zones = {zone["name"]: zone for zone in fw["zone"]}
|
||||
services = {svc["name"]: svc for svc in fw.get("service", [])}
|
||||
|
||||
# Verify custom service exists
|
||||
assert "myapp" in services, "Custom service myapp not found"
|
||||
custom_service = services["myapp"]
|
||||
assert len(custom_service["port"]) == 1
|
||||
port_entry = next(iter(custom_service["port"]))
|
||||
assert port_entry["proto"] == "tcp"
|
||||
assert int(port_entry["lower"]) == CUSTOM_PORT
|
||||
|
||||
# Verify zone assignments
|
||||
untrusted_zone = zones["untrusted"]
|
||||
trusted_zone = zones["trusted"]
|
||||
|
||||
assert data1_if in untrusted_zone["interface"]
|
||||
assert data2_if in trusted_zone["interface"]
|
||||
|
||||
# Check services safely - they may not exist in operational data if empty
|
||||
trusted_services = trusted_zone.get("service", [])
|
||||
untrusted_services = untrusted_zone.get("service", [])
|
||||
|
||||
assert "myapp" in trusted_services, f"Custom service should be in trusted zone, got: {trusted_services}"
|
||||
assert "myapp" not in untrusted_services, f"Custom service should not be in untrusted zone, got: {untrusted_services}"
|
||||
|
||||
with infamy.IsolatedMacVlan(host_data1) as ns1:
|
||||
ns1.addip(DATA1_HOST_V4, prefix_length=24, proto="ipv4")
|
||||
ns1.addip(DATA1_HOST_V6, prefix_length=64, proto="ipv6")
|
||||
|
||||
with infamy.IsolatedMacVlan(host_data2) as ns2:
|
||||
ns2.addip(DATA2_HOST_V4, prefix_length=24, proto="ipv4")
|
||||
ns2.addip(DATA2_HOST_V6, prefix_length=64, proto="ipv6")
|
||||
|
||||
with test.step("Verify IPv4/IPv6 connectivity and custom service restrictions"):
|
||||
# print(f"Testing IPv4 connectivity: {DATA1_HOST_V4} -> {DATA1_TARGET_V4}")
|
||||
# print(f"Testing IPv4 connectivity: {DATA2_HOST_V4} -> {DATA2_TARGET_V4}")
|
||||
ns1.must_reach(DATA1_TARGET_V4, timeout=5)
|
||||
ns2.must_reach(DATA2_TARGET_V4, timeout=5)
|
||||
|
||||
# print(f"Testing IPv6 connectivity: {DATA1_HOST_V6} -> {DATA1_TARGET_V6}")
|
||||
# print(f"Testing IPv6 connectivity: {DATA2_HOST_V6} -> {DATA2_TARGET_V6}")
|
||||
ns1.must_reach(DATA1_TARGET_V6, timeout=5)
|
||||
ns2.must_reach(DATA2_TARGET_V6, timeout=5)
|
||||
|
||||
firewall_ns1 = infamy.Firewall(ns1, None)
|
||||
firewall_ns2 = infamy.Firewall(ns2, None)
|
||||
|
||||
ok, ports = firewall_ns1.verify_allowed(DATA1_TARGET_V4,
|
||||
[(CUSTOM_PORT, "tcp", "myapp")])
|
||||
ok, ports = firewall_ns2.verify_allowed(DATA2_TARGET_V4,
|
||||
[(CUSTOM_PORT, "tcp", "myapp")])
|
||||
|
||||
with test.step("Verify IPv6 custom service functionality"):
|
||||
ok, ports = firewall_ns1.verify_allowed(DATA1_TARGET_V6,
|
||||
[(CUSTOM_PORT, "tcp", "myapp")])
|
||||
ok, ports = firewall_ns2.verify_allowed(DATA2_TARGET_V6,
|
||||
[(CUSTOM_PORT, "tcp", "myapp")])
|
||||
|
||||
with test.step("Perform dynamic zone migration"):
|
||||
target.delete_xpath(f"/infix-firewall:firewall/zone[name='untrusted']/interface[.='{data1_if}']")
|
||||
target.put_config_dict("infix-firewall", {
|
||||
"firewall": {
|
||||
"zone": [{
|
||||
"name": "trusted",
|
||||
"interface": [data1_if]
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
infamy.Firewall.wait_for_operational(target, {
|
||||
"untrusted": {"action": "accept"},
|
||||
"trusted": {"action": "accept"}
|
||||
})
|
||||
|
||||
with test.step("Verify connectivity after zone migration"):
|
||||
ns1.must_reach(DATA1_TARGET_V4, timeout=3)
|
||||
ns1.must_reach(DATA1_TARGET_V6, timeout=3)
|
||||
ns2.must_reach(DATA2_TARGET_V4, timeout=3)
|
||||
ns2.must_reach(DATA2_TARGET_V6, timeout=3)
|
||||
|
||||
with test.step("Verify custom service from migrated interface"):
|
||||
firewall_migrated = infamy.Firewall(ns1, None)
|
||||
ok, ports = firewall_migrated.verify_allowed(DATA1_TARGET_V4,
|
||||
[(CUSTOM_PORT, "tcp", "myapp")])
|
||||
assert ok, f"Custom service should work on IPv4 after zone migration"
|
||||
|
||||
ok, ports = firewall_migrated.verify_allowed(DATA1_TARGET_V6,
|
||||
[(CUSTOM_PORT, "tcp", "myapp")])
|
||||
assert ok, f"Custom service should work on IPv6 after zone migration"
|
||||
|
||||
with test.step("Verify operational state reflects zone changes"):
|
||||
data = target.get_data("/infix-firewall:firewall")
|
||||
fw = data["firewall"]
|
||||
zones = {zone["name"]: zone for zone in fw["zone"]}
|
||||
|
||||
trusted_zone = zones["trusted"]
|
||||
untrusted_zone = zones["untrusted"]
|
||||
|
||||
assert data1_if in trusted_zone["interface"], "data1_if should be in trusted zone"
|
||||
assert data2_if in trusted_zone["interface"], "data2_if should be in trusted zone"
|
||||
assert data1_if not in untrusted_zone.get("interface", []), "data1_if should no longer be in untrusted zone"
|
||||
|
||||
trusted_services = trusted_zone.get("service", [])
|
||||
assert "myapp" in trusted_services, f"Custom service should be available in trusted zone, got: {trusted_services}"
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1,24 @@
|
||||
graph "1x3" {
|
||||
layout = "neato";
|
||||
overlap = false;
|
||||
esep = "+80";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt> mgmt | <data1> data1 | <data2> data2 }",
|
||||
pos="1,1!",
|
||||
requires="controller"
|
||||
];
|
||||
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data1> data1 | <data2> data2 } | target",
|
||||
pos="3,1!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:data1 -- target:data1 [color=red, fontcolor=red, taillabel="10.1.1.0/24, fd01:1:1::/64"]
|
||||
host:data2 -- target:data2 [color=green, fontcolor=green, taillabel="10.2.2.0/24, fd02:2:2::/64"]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 1x3 Pages: 1 -->
|
||||
<svg width="440pt" height="78pt"
|
||||
viewBox="0.00 0.00 440.03 78.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 74)">
|
||||
<title>1x3</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-74 436.03,-74 436.03,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-69.5 108,-69.5 108,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-69.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="50,-46.5 108,-46.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="50,-23.5 108,-23.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
</g>
|
||||
<!-- target -->
|
||||
<g id="node2" class="node">
|
||||
<title>target</title>
|
||||
<polygon fill="none" stroke="black" points="308.03,-0.5 308.03,-69.5 432.03,-69.5 432.03,-0.5 308.03,-0.5"/>
|
||||
<text text-anchor="middle" x="337.03" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="308.03,-46.5 366.03,-46.5 "/>
|
||||
<text text-anchor="middle" x="337.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="308.03,-23.5 366.03,-23.5 "/>
|
||||
<text text-anchor="middle" x="337.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
<polyline fill="none" stroke="black" points="366.03,-0.5 366.03,-69.5 "/>
|
||||
<text text-anchor="middle" x="399.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--target:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-58C108,-58 308.03,-58 308.03,-58"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data1--target:data1</title>
|
||||
<path fill="none" stroke="red" stroke-width="2" d="M108,-35C108,-35 308.03,-35 308.03,-35"/>
|
||||
<text text-anchor="middle" x="198.5" y="-38.8" font-family="DejaVu Serif, Book" font-size="14.00" fill="red">10.1.1.0/24, fd01:1:1::/64</text>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:data2--target:data2</title>
|
||||
<path fill="none" stroke="green" stroke-width="2" d="M108,-12C108,-12 308.03,-12 308.03,-12"/>
|
||||
<text text-anchor="middle" x="198.5" y="-15.8" font-family="DejaVu Serif, Book" font-size="14.00" fill="green">10.2.2.0/24, fd02:2:2::/64</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
Reference in New Issue
Block a user