1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/interfaces/test_forms.py
catborise 525018dd99 fix(interfaces): validate IPv6 address and gateway of host interfaces
The form checked both with re.match("^[0-9a-f./:]+|^$"), which tests
only how a value starts: "2001:db8::10/64'/><evil/>" passed and went
into the interface XML, which is built with f-strings, while upper-case
hex was refused. An address without a prefix passed too and then failed
with a ValueError when the XML was built.

The address is parsed with ipaddress.IPv6Interface and stored as
address/prefix (/128 when none is given), the gateway with
IPv6Address. A static configuration needs an address, and the XML gets
a route only when a gateway is given instead of gateway=''. The HTML
patterns accept upper-case hex.

Eight of the ten form and XML checks fail on the previous code.
2026-10-04 17:29:59 +03:00

69 lines
2.7 KiB
Python

"""Host interface form: IPv6 values are validated before they go into the interface XML."""
from unittest.mock import MagicMock
from django.test import SimpleTestCase
from lxml import etree
from vrtManager.interface import wvmInterfaces
from interfaces.forms import AddInterface
BASE = {
"name": "br1", "itype": "bridge", "start_mode": "onboot", "netdev": "eth1",
"ipv4_type": "static", "ipv4_addr": "192.0.2.10/24", "ipv4_gw": "192.0.2.1",
"ipv6_type": "static", "stp": "on", "delay": 0,
}
def form(**ipv6):
return AddInterface(data={**BASE, **ipv6})
class Ipv6FormTestCase(SimpleTestCase):
def test_valid_addresses_are_normalized(self):
f = form(ipv6_addr="2001:DB8::10/64", ipv6_gw="2001:DB8::1")
self.assertTrue(f.is_valid(), f.errors)
self.assertEqual(f.cleaned_data["ipv6_addr"], "2001:db8::10/64")
self.assertEqual(f.cleaned_data["ipv6_gw"], "2001:db8::1")
def test_address_without_prefix_gets_one(self):
f = form(ipv6_addr="2001:db8::10", ipv6_gw="")
self.assertTrue(f.is_valid(), f.errors)
self.assertEqual(f.cleaned_data["ipv6_addr"], "2001:db8::10/128")
def test_values_that_only_start_like_an_address_are_rejected(self):
for field, value in (
("ipv6_addr", "2001:db8::10/64'/><evil/>"),
("ipv6_gw", "2001:db8::1'/><evil/>"),
("ipv6_addr", "2001:db8::zz/64"),
("ipv6_addr", "192.0.2.10/24"),
):
with self.subTest(field=field, value=value):
f = form(**{"ipv6_addr": "2001:db8::10/64", "ipv6_gw": "", field: value})
self.assertFalse(f.is_valid())
self.assertIn(field, f.errors)
def test_static_needs_an_address(self):
f = form(ipv6_addr="", ipv6_gw="")
self.assertFalse(f.is_valid())
self.assertIn("ipv6_addr", f.errors)
def test_dhcp_needs_no_address(self):
self.assertTrue(form(ipv6_type="dhcp", ipv6_addr="", ipv6_gw="").is_valid())
class Ipv6XmlTestCase(SimpleTestCase):
def xml(self, gw):
conn = wvmInterfaces.__new__(wvmInterfaces)
conn.define_iface = MagicMock()
conn.get_iface = MagicMock()
conn.create_iface("br1", "bridge", "onboot", "eth1", "none", "", "", "static", "2001:db8::10/64", gw, "on", 0)
return etree.fromstring(conn.define_iface.call_args[0][0])
def test_static_address_with_gateway(self):
proto = self.xml("2001:db8::1").find("protocol[@family='ipv6']")
self.assertEqual(proto.find("ip").attrib, {"address": "2001:db8::10", "prefix": "64"})
self.assertEqual(proto.find("route").get("gateway"), "2001:db8::1")
def test_no_empty_route_without_gateway(self):
self.assertIsNone(self.xml("").find("protocol[@family='ipv6']/route"))