1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/computes/validators.py
catborise 2dab23cd9e fix(gstfsd): only the panel, with the host's key, can change a VM's disk
gstfsd, the root service on each compute that rewrites a shut-off VM's
/etc/shadow and authorized_keys, did what any client on its port asked.
Now it needs GSTFSD_KEY (64 hexadecimal characters, in the unit's
/etc/default/webvirt-gstfsd) and does not start without it. Each
connection gets a new challenge; the panel signs its request for it
with HMAC-SHA256 and gstfsd checks the signature before any change, so
a request recorded on one connection is refused on another. gstfsd
signs its reply for the same challenge and the panel's nonce, and the
panel accepts only that reply.

Each compute has a gstfsd key, the host's GSTFSD_KEY: the compute forms
and the API take it and never show it, and an empty value on an edit
keeps it. The bootstrap script makes the key file once, readable by
root only; the README shows how to make it and where the panel takes
it. The traffic is still not encrypted: a remote panel's port stays
open to the panel's address only.
2026-10-31 01:53:43 +03:00

60 lines
2.4 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"))
def validate_gstfsd_key(value):
"""The key gstfsd on the host signs with: GSTFSD_KEY in its
/etc/default/webvirt-gstfsd, 64 lowercase hexadecimal characters."""
if not re.fullmatch(r"[0-9a-f]{64}", value):
raise ValidationError(_("Enter the 64 hexadecimal characters of GSTFSD_KEY in the host's /etc/default/webvirt-gstfsd"))