mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
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.
53 lines
2.1 KiB
Python
53 lines
2.1 KiB
Python
import re
|
|
from ipaddress import ip_address
|
|
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
# one or more dot-separated RFC 1123 labels, optional trailing dot
|
|
_hostname = re.compile(r"[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*\.?")
|
|
_login = re.compile(r"[A-Za-z0-9._][A-Za-z0-9._-]*")
|
|
_name = re.compile(r"[A-Za-z0-9._-]+")
|
|
|
|
|
|
def validate_hostname(value):
|
|
"""An IPv4 address or a host name that goes safely into a libvirt
|
|
connection URI. No scheme, port, path, query, whitespace or IPv6 literal:
|
|
those would change the URI (an injected ?command=, ?no_verify=, a port or
|
|
a userinfo separator). IPv6 is rejected until the URI building brackets
|
|
it."""
|
|
try:
|
|
if ip_address(value).version == 4:
|
|
return
|
|
except ValueError:
|
|
if _hostname.fullmatch(value):
|
|
return
|
|
raise ValidationError(_("Enter an IPv4 address or a host name, without a scheme, port or path"))
|
|
|
|
|
|
def validate_login(value):
|
|
"""A user name that goes safely into a libvirt connection URI and an ssh
|
|
command line. Empty is allowed (TCP falls back to the server's own
|
|
authentication, the socket transport ignores the login); otherwise only
|
|
letters, digits, ".", "_" and "-", and never a leading "-" (an ssh
|
|
option)."""
|
|
if value and not _login.fullmatch(value):
|
|
raise ValidationError(
|
|
_("The login may contain only letters, digits, '.', '_' and '-', and may not start with '-'")
|
|
)
|
|
|
|
|
|
def validate_name(value):
|
|
if not _name.fullmatch(value):
|
|
raise ValidationError(_("The name must not contain any special characters"))
|
|
|
|
|
|
def validate_migration_address(value):
|
|
"""An IP address (no IPv6 scope) or a host name: no scheme, port or brackets."""
|
|
try:
|
|
if not getattr(ip_address(value), "scope_id", None):
|
|
return
|
|
except ValueError:
|
|
if _hostname.fullmatch(value):
|
|
return
|
|
raise ValidationError(_("Enter an IP address or a host name, without a scheme or port"))
|