mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 12:46:49 +00:00
fix(computes): a compute's hostname and login cannot inject a connection URI or ssh option
A compute's hostname and login go unquoted into the libvirt connection URI (qemu+ssh://login@host/system, qemu+tcp://host/system, qemu+tls://...) and, for an SSH compute, into the ssh command line of the DRBD status view. They were barely validated: validate_hostname used re.match with a negated class, so it caught only a leading special character, and login was never checked. A hostname like "192.0.2.1/system?command=/path" injected a libvirt URI parameter (verified: connecting ran the named command through the ssh wrapper), and TLS/SSH parameters such as no_verify, pkipath, socket or netcat could be added the same way; a login like "-oProxyCommand=..." became an ssh option. The DRF ComputeSerializer applied neither check, and validate_name tested a string literal instead of its argument, so it never raised. validate_hostname now accepts only an IPv4 address or a host name with fullmatch (no scheme, port, path, query, whitespace or IPv6 literal; IPv6 is left out until the URI building brackets it). validate_login accepts an empty value (TCP falls back to the server's authentication, the socket transport ignores the login) or a plain name with no leading "-". Both sit on the Compute model fields, so the forms and the DRF serializer (which copies model validators onto its fields) reject the values; a migration records the metadata. validate_name checks its argument. drbd_status validates the login and hostname before building login@host and ends ssh option parsing with "--"; an existing or bypassed bad row is skipped rather than run. Tests: hostname/login validators accept real values and reject URI and option injection; the SSH form and the serializer (create, and an empty TCP login) enforce them; drbd_status skips an injecting target. Checked the real test computes still validate. The new tests fail on the previous code.
This commit is contained in:
parent
c7a7c843fb
commit
17016e90cd
7 changed files with 189 additions and 21 deletions
|
|
@ -317,3 +317,43 @@ class ComputeConcurrencyTestCase(TransactionTestCase):
|
|||
with libvirt_compute_lock(compute, timeout=1.0):
|
||||
executed.append("deep")
|
||||
self.assertEqual(executed, ["outer", "inner", "deep"])
|
||||
|
||||
|
||||
class ComputeInputValidationTestCase(TestCase):
|
||||
"""hostname and login reach libvirt URIs and ssh unquoted; the form and
|
||||
the API reject values that would inject a URI parameter or an ssh option."""
|
||||
|
||||
def test_form_rejects_injecting_hostname(self):
|
||||
from computes.forms import SshComputeForm
|
||||
|
||||
form = SshComputeForm(
|
||||
data={"name": "c", "hostname": "192.0.2.1/system?command=/evil", "login": "root", "type": 2}
|
||||
)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn("hostname", form.errors)
|
||||
|
||||
def test_api_rejects_injecting_hostname_and_login(self):
|
||||
from computes.api.serializers import ComputeSerializer
|
||||
|
||||
for field, data in (
|
||||
("hostname", {"name": "c", "hostname": "192.0.2.1/system?command=/evil&a=", "login": "root", "type": 2}),
|
||||
("login", {"name": "c", "hostname": "192.0.2.1", "login": "-oProxyCommand=x", "type": 2}),
|
||||
):
|
||||
with self.subTest(field=field):
|
||||
serializer = ComputeSerializer(data=data)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertIn(field, serializer.errors)
|
||||
|
||||
def test_api_accepts_valid_values(self):
|
||||
from computes.api.serializers import ComputeSerializer
|
||||
|
||||
serializer = ComputeSerializer(
|
||||
data={"name": "c", "hostname": "host.example.com", "login": "root", "type": 2}
|
||||
)
|
||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||
|
||||
def test_api_accepts_empty_login_for_tcp(self):
|
||||
from computes.api.serializers import ComputeSerializer
|
||||
|
||||
serializer = ComputeSerializer(data={"name": "c", "hostname": "192.0.2.1", "login": "", "type": 1})
|
||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue