summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDevaev Maxim <[email protected]>2020-04-23 11:17:22 +0300
committerDevaev Maxim <[email protected]>2020-04-23 11:17:31 +0300
commit75669466cf6b68fbe209a6d2232aec6d49a51044 (patch)
tree74bdd3064eb76e0d5b6cabd2c6df07bc425e8ea5
parent820ef178710d8442e30c5b23d0ac0cb90be5150c (diff)
vnc: anon tls encryption
-rw-r--r--kvmd/apps/__init__.py6
-rw-r--r--kvmd/apps/vnc/__init__.py13
-rw-r--r--kvmd/apps/vnc/rfb/__init__.py29
-rw-r--r--kvmd/apps/vnc/rfb/stream.py26
-rw-r--r--kvmd/apps/vnc/server.py69
-rw-r--r--kvmd/validators/net.py12
-rw-r--r--testenv/tests/validators/test_net.py13
7 files changed, 131 insertions, 37 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py
index 1c29b182..7d9b7425 100644
--- a/kvmd/apps/__init__.py
+++ b/kvmd/apps/__init__.py
@@ -69,6 +69,7 @@ from ..validators.net import valid_ip_or_host
from ..validators.net import valid_ip
from ..validators.net import valid_port
from ..validators.net import valid_mac
+from ..validators.net import valid_ssl_ciphers
from ..validators.kvm import valid_stream_quality
from ..validators.kvm import valid_stream_fps
@@ -328,8 +329,11 @@ def _get_config_scheme() -> Dict:
"server": {
"host": Option("::", type=valid_ip_or_host),
"port": Option(5900, type=valid_port),
- # TODO: timeout
"max_clients": Option(10, type=(lambda arg: valid_number(arg, min=1))),
+ "tls": {
+ "ciphers": Option("ALL:@SECLEVEL=0", type=valid_ssl_ciphers),
+ "timeout": Option(5.0, type=valid_float_f01),
+ },
},
"kvmd": {
diff --git a/kvmd/apps/vnc/__init__.py b/kvmd/apps/vnc/__init__.py
index 8d863bcc..8bd9be63 100644
--- a/kvmd/apps/vnc/__init__.py
+++ b/kvmd/apps/vnc/__init__.py
@@ -42,10 +42,17 @@ def main(argv: Optional[List[str]]=None) -> None:
# pylint: disable=protected-access
VncServer(
+ host=config.server.host,
+ port=config.server.port,
+ max_clients=config.server.max_clients,
+
+ tls_ciphers=config.server.tls.ciphers,
+ tls_timeout=config.server.tls.timeout,
+
+ desired_fps=config.desired_fps,
+ symmap=build_symmap(config.keymap),
+
kvmd=KvmdClient(**config.kvmd._unpack()),
streamer=StreamerClient(**config.streamer._unpack()),
vnc_auth_manager=VncAuthManager(**config.auth.vncauth._unpack()),
- desired_fps=config.desired_fps,
- symmap=build_symmap(config.keymap),
- **config.server._unpack(),
).run()
diff --git a/kvmd/apps/vnc/rfb/__init__.py b/kvmd/apps/vnc/rfb/__init__.py
index 698b5084..77f29422 100644
--- a/kvmd/apps/vnc/rfb/__init__.py
+++ b/kvmd/apps/vnc/rfb/__init__.py
@@ -21,6 +21,7 @@
import asyncio
+import ssl
from typing import Tuple
from typing import List
@@ -45,7 +46,7 @@ from .stream import RfbClientStream
# =====
-class RfbClient(RfbClientStream):
+class RfbClient(RfbClientStream): # pylint: disable=too-many-instance-attributes
# https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst
# https://www.toptal.com/java/implementing-remote-framebuffer-server-java
# https://github.com/TigerVNC/tigervnc
@@ -54,6 +55,8 @@ class RfbClient(RfbClientStream):
self,
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
+ tls_ciphers: str,
+ tls_timeout: float,
width: int,
height: int,
@@ -63,6 +66,9 @@ class RfbClient(RfbClientStream):
super().__init__(reader, writer)
+ self.__tls_ciphers = tls_ciphers
+ self.__tls_timeout = tls_timeout
+
self._width = width
self._height = height
self.__name = name
@@ -98,7 +104,7 @@ class RfbClient(RfbClientStream):
raise
except RfbConnectionError as err:
logger.info("[%s] Client %s: Gone (%s): Disconnected", name, self._remote, str(err))
- except RfbError as err:
+ except (RfbError, ssl.SSLError) as err:
logger.error("[%s] Client %s: %s: Disconnected", name, self._remote, str(err))
except Exception:
logger.exception("[%s] Unhandled exception with client %s: Disconnected", name, self._remote)
@@ -225,13 +231,19 @@ class RfbClient(RfbClientStream):
await self._write_struct("B", 0)
- auth_types = {256: ("VeNCrypt/Plain", self.__handshake_security_vencrypt_userpass)}
+ auth_types = {
+ 256: ("VeNCrypt/Plain", False, self.__handshake_security_vencrypt_userpass),
+ 259: ("VeNCrypt/TLSPlain", True, self.__handshake_security_vencrypt_userpass),
+ }
if self.__vnc_passwds:
# Vinagre не умеет работать с VNC Auth через VeNCrypt, но это его проблемы,
# так как он своеобразно трактует рекомендации VeNCrypt.
# Подробнее: https://bugzilla.redhat.com/show_bug.cgi?id=692048
# Hint: используйте любой другой нормальный VNC-клиент.
- auth_types[2] = ("VeNCrypt/VNCAuth", self.__handshake_security_vnc_auth)
+ auth_types.update({
+ 2: ("VeNCrypt/VNCAuth", False, self.__handshake_security_vnc_auth),
+ 258: ("VeNCrypt/TLSVNCAuth", True, self.__handshake_security_vnc_auth),
+ })
await self._write_struct("B" + "L" * len(auth_types), len(auth_types), *auth_types)
@@ -239,8 +251,15 @@ class RfbClient(RfbClientStream):
if auth_type not in auth_types:
raise RfbError(f"Invalid VeNCrypt auth type: {auth_type}")
- (auth_name, handler) = auth_types[auth_type]
+ (auth_name, tls, handler) = auth_types[auth_type]
get_logger(0).info("[main] Client %s: Using %s auth type", self._remote, auth_name)
+
+ if tls:
+ await self._write_struct("B", 1) # Ack
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ ssl_context.set_ciphers(self.__tls_ciphers)
+ await self._start_tls(ssl_context, self.__tls_timeout)
+
await handler()
async def __handshake_security_vencrypt_userpass(self) -> None:
diff --git a/kvmd/apps/vnc/rfb/stream.py b/kvmd/apps/vnc/rfb/stream.py
index 843cfe54..49d86931 100644
--- a/kvmd/apps/vnc/rfb/stream.py
+++ b/kvmd/apps/vnc/rfb/stream.py
@@ -21,6 +21,7 @@
import asyncio
+import ssl
import struct
from typing import Tuple
@@ -102,6 +103,31 @@ class RfbClientStream:
# =====
+ async def _start_tls(self, ssl_context: ssl.SSLContext, ssl_timeout: float) -> None:
+ loop = asyncio.get_event_loop()
+
+ ssl_reader = asyncio.StreamReader()
+ protocol = asyncio.StreamReaderProtocol(ssl_reader)
+
+ transport = await loop.start_tls(
+ self.__writer.transport,
+ protocol,
+ ssl_context,
+ server_side=True,
+ ssl_handshake_timeout=ssl_timeout,
+ )
+
+ ssl_reader.set_transport(transport)
+ ssl_writer = asyncio.StreamWriter(
+ transport=transport,
+ protocol=protocol,
+ reader=ssl_reader,
+ loop=loop,
+ )
+
+ self.__reader = ssl_reader
+ self.__writer = ssl_writer
+
def _close(self) -> None:
try:
self.__writer.close()
diff --git a/kvmd/apps/vnc/server.py b/kvmd/apps/vnc/server.py
index 084f9b0f..36be7944 100644
--- a/kvmd/apps/vnc/server.py
+++ b/kvmd/apps/vnc/server.py
@@ -59,29 +59,40 @@ class _SharedParams:
class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
- def __init__(
+ def __init__( # pylint: disable=too-many-arguments
self,
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
+ tls_ciphers: str,
+ tls_timeout: float,
+
+ desired_fps: int,
+ symmap: Dict[int, str],
kvmd: KvmdClient,
streamer: StreamerClient,
- desired_fps: int,
- symmap: Dict[int, str],
vnc_credentials: Dict[str, VncAuthKvmdCredentials],
-
shared_params: _SharedParams,
) -> None:
self.__vnc_credentials = vnc_credentials
- super().__init__(reader, writer, vnc_passwds=list(vnc_credentials), **dataclasses.asdict(shared_params))
+ super().__init__(
+ reader=reader,
+ writer=writer,
+ tls_ciphers=tls_ciphers,
+ tls_timeout=tls_timeout,
+ vnc_passwds=list(vnc_credentials),
+ **dataclasses.asdict(shared_params),
+ )
- self.__kvmd = kvmd
- self.__streamer = streamer
self.__desired_fps = desired_fps
self.__symmap = symmap
+
+ self.__kvmd = kvmd
+ self.__streamer = streamer
+
self.__shared_params = shared_params
self.__authorized = asyncio.Future() # type: ignore
@@ -271,32 +282,46 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
# =====
class VncServer: # pylint: disable=too-many-instance-attributes
- def __init__(
+ def __init__( # pylint: disable=too-many-arguments
self,
host: str,
port: int,
max_clients: int,
- kvmd: KvmdClient,
- streamer: StreamerClient,
- vnc_auth_manager: VncAuthManager,
+ tls_ciphers: str,
+ tls_timeout: float,
desired_fps: int,
symmap: Dict[int, str],
+
+ kvmd: KvmdClient,
+ streamer: StreamerClient,
+ vnc_auth_manager: VncAuthManager,
) -> None:
self.__host = host
self.__port = port
self.__max_clients = max_clients
- self.__kvmd = kvmd
- self.__streamer = streamer
self.__vnc_auth_manager = vnc_auth_manager
- self.__desired_fps = desired_fps
- self.__symmap = symmap
+ shared_params = _SharedParams()
+
+ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
+ await _Client(
+ reader=reader,
+ writer=writer,
+ tls_ciphers=tls_ciphers,
+ tls_timeout=tls_timeout,
+ desired_fps=desired_fps,
+ symmap=symmap,
+ kvmd=kvmd,
+ streamer=streamer,
+ vnc_credentials=(await self.__vnc_auth_manager.read_credentials())[0],
+ shared_params=shared_params,
+ ).run()
- self.__shared_params = _SharedParams()
+ self.__handle_client = handle_client
def run(self) -> None:
logger = get_logger(0)
@@ -332,15 +357,3 @@ class VncServer: # pylint: disable=too-many-instance-attributes
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
loop.close()
logger.info("Bye-bye")
-
- async def __handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
- await _Client(
- reader=reader,
- writer=writer,
- kvmd=self.__kvmd,
- streamer=self.__streamer,
- desired_fps=self.__desired_fps,
- symmap=self.__symmap,
- vnc_credentials=(await self.__vnc_auth_manager.read_credentials())[0],
- shared_params=self.__shared_params,
- ).run() # type: ignore
diff --git a/kvmd/validators/net.py b/kvmd/validators/net.py
index 247122cd..7862c391 100644
--- a/kvmd/validators/net.py
+++ b/kvmd/validators/net.py
@@ -21,11 +21,13 @@
import ipaddress
+import ssl
from typing import List
from typing import Callable
from typing import Any
+from . import ValidatorError
from . import check_re_match
from . import check_any
@@ -75,3 +77,13 @@ def valid_port(arg: Any) -> int:
def valid_mac(arg: Any) -> str:
pattern = ":".join([r"[0-9a-fA-F]{2}"] * 6)
return check_re_match(arg, "MAC address", pattern).lower()
+
+
+def valid_ssl_ciphers(arg: Any) -> str:
+ name = "SSL ciphers"
+ arg = valid_stripped_string_not_empty(arg, name)
+ try:
+ ssl.SSLContext().set_ciphers(arg)
+ except Exception as err:
+ raise ValidatorError(f"The argument {arg!r} is not a valid {name}: {str(err)}")
+ return arg
diff --git a/testenv/tests/validators/test_net.py b/testenv/tests/validators/test_net.py
index 269db9e1..486154fc 100644
--- a/testenv/tests/validators/test_net.py
+++ b/testenv/tests/validators/test_net.py
@@ -30,6 +30,7 @@ from kvmd.validators.net import valid_ip
from kvmd.validators.net import valid_rfc_host
from kvmd.validators.net import valid_port
from kvmd.validators.net import valid_mac
+from kvmd.validators.net import valid_ssl_ciphers
# =====
@@ -142,3 +143,15 @@ def test_ok__valid_mac(arg: Any) -> None:
def test_fail__valid_mac(arg: Any) -> None:
with pytest.raises(ValidatorError):
print(valid_mac(arg))
+
+
+# =====
[email protected]("arg", ["ALL", " ALL:@SECLEVEL=0 "])
+def test_ok__valid_ssl_ciphers(arg: Any) -> None:
+ assert valid_ssl_ciphers(arg) == str(arg).strip()
+
+
[email protected]("arg", ["test", "all", "", None])
+def test_fail__valid_ssl_ciphers(arg: Any) -> None:
+ with pytest.raises(ValidatorError):
+ print(valid_ssl_ciphers(arg))