Skip to content

Commit 46ef5fd

Browse files
committed
vnc: using usc auth
1 parent c8cf06e commit 46ef5fd

File tree

7 files changed

+70
-149
lines changed

7 files changed

+70
-149
lines changed

configs/kvmd/vncpasswd

+6-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
# This file describes the credentials for VNCAuth. The left part before arrow is a passphrase
2-
# for VNCAuth. The right part is username and password with which the user can access to KVMD API.
3-
# The arrow is used as a separator and shows the relationship of user registrations on the system.
1+
# This file contains passwords for the legacy VNCAuth, one per line.
2+
# The passwords are NOT encrypted.
43
#
5-
# Never use the same passwords for VNC and IPMI users. This default configuration is shown here
6-
# for example only.
4+
# WARNING! The VNCAuth method is NOT secure and should not be used at all.
5+
# But we support it for compatibility with some clients.
76
#
8-
# If this file does not contain any entries, VNCAuth will be disabled and you will only be able
9-
# to login in using your KVMD username and password using VeNCrypt methods.
7+
# NEVER use the same passwords for KVMD, IPMI and VNCAuth users.
108

11-
# pa$$phr@se -> admin:password
12-
admin -> admin:admin
9+
admin

kvmd/apps/__init__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ def _get_config_scheme() -> dict:
364364
"usc": {
365365
"users": Option([
366366
"kvmd-ipmi",
367+
"kvmd-vnc",
367368
], type=valid_users_list), # PiKVM username has a same regex as a UNIX username
368369
"groups": Option([], type=valid_users_list), # groupname has a same regex as a username
369370
},
@@ -798,8 +799,8 @@ def _get_config_scheme() -> dict:
798799

799800
"auth": {
800801
"vncauth": {
801-
"enabled": Option(False, type=valid_bool),
802-
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="path"),
802+
"enabled": Option(False, type=valid_bool, unpack_as="vncpass_enabled"),
803+
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="vncpass_path"),
803804
},
804805
"vencrypt": {
805806
"enabled": Option(True, type=valid_bool, unpack_as="vencrypt_enabled"),

kvmd/apps/vnc/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
from .. import init
3232

33-
from .vncauth import VncAuthManager
3433
from .server import VncServer
3534

3635

@@ -76,8 +75,8 @@ def make_memsink_streamer(name: str, fmt: int) -> (MemsinkStreamerClient | None)
7675

7776
kvmd=KvmdClient(user_agent=user_agent, **config.kvmd._unpack()),
7877
streamers=streamers,
79-
vnc_auth_manager=VncAuthManager(**config.auth.vncauth._unpack()),
8078

8179
**config.server.keepalive._unpack(),
80+
**config.auth.vncauth._unpack(),
8281
**config.auth.vencrypt._unpack(),
8382
).run()

kvmd/apps/vnc/rfb/__init__.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ def __init__( # pylint: disable=too-many-arguments
6767
name: str,
6868
scroll_rate: int,
6969
allow_cut_after: float,
70-
vnc_passwds: list[str],
70+
71+
vncpasses: set[str],
7172
vencrypt: bool,
7273
none_auth_only: bool,
7374
) -> None:
@@ -84,7 +85,8 @@ def __init__( # pylint: disable=too-many-arguments
8485
self.__name = name
8586
self.__scroll_rate = scroll_rate
8687
self.__allow_cut_after = allow_cut_after
87-
self.__vnc_passwds = vnc_passwds
88+
89+
self.__vncpasses = vncpasses
8890
self.__vencrypt = vencrypt
8991
self.__none_auth_only = none_auth_only
9092

@@ -145,10 +147,10 @@ async def __main_task_loop(self) -> None:
145147
async def _authorize_userpass(self, user: str, passwd: str) -> bool:
146148
raise NotImplementedError
147149

148-
async def _on_authorized_vnc_passwd(self, passwd: str) -> str:
150+
async def _on_authorized_vncpass(self) -> None:
149151
raise NotImplementedError
150152

151-
async def _on_authorized_none(self) -> bool:
153+
async def _authorize_none(self) -> bool:
152154
raise NotImplementedError
153155

154156
# =====
@@ -260,7 +262,7 @@ async def __handshake_security(self) -> None:
260262
sec_types[19] = ("VeNCrypt", self.__handshake_security_vencrypt)
261263
if self.__none_auth_only:
262264
sec_types[1] = ("None", self.__handshake_security_none)
263-
elif self.__vnc_passwds:
265+
elif self.__vncpasses:
264266
sec_types[2] = ("VNCAuth", self.__handshake_security_vnc_auth)
265267

266268
if not sec_types:
@@ -306,7 +308,7 @@ async def __handshake_security_vencrypt(self) -> None: # pylint: disable=too-ma
306308
if self.__x509_cert_path:
307309
auth_types[262] = ("VeNCrypt/X509Plain", 2, self.__handshake_security_vencrypt_userpass)
308310
auth_types[259] = ("VeNCrypt/TLSPlain", 1, self.__handshake_security_vencrypt_userpass)
309-
if self.__vnc_passwds:
311+
if self.__vncpasses:
310312
# Некоторые клиенты не умеют работать с нешифрованными соединениями внутри VeNCrypt:
311313
# - https://github.com/LibVNC/libvncserver/issues/458
312314
# - https://bugzilla.redhat.com/show_bug.cgi?id=692048
@@ -356,7 +358,7 @@ async def __handshake_security_vencrypt_userpass(self) -> None:
356358
)
357359

358360
async def __handshake_security_none(self) -> None:
359-
allow = await self._on_authorized_none()
361+
allow = await self._authorize_none()
360362
await self.__handshake_security_send_result(
361363
allow=allow,
362364
allow_msg="NoneAuth access granted",
@@ -368,20 +370,19 @@ async def __handshake_security_vnc_auth(self) -> None:
368370
challenge = rfb_make_challenge()
369371
await self._write_struct("VNCAuth challenge request", "", challenge)
370372

371-
user = ""
373+
allow = False
372374
response = (await self._read_struct("VNCAuth challenge response", "16s"))[0]
373-
for passwd in self.__vnc_passwds:
375+
for passwd in self.__vncpasses:
374376
passwd_bytes = passwd.encode("utf-8", errors="ignore")
375377
if rfb_encrypt_challenge(challenge, passwd_bytes) == response:
376-
user = await self._on_authorized_vnc_passwd(passwd)
377-
if user:
378-
assert user == user.strip()
378+
await self._on_authorized_vncpass()
379+
allow = True
379380
break
380381

381382
await self.__handshake_security_send_result(
382-
allow=bool(user),
383-
allow_msg=f"VNCAuth access granted for user {user!r}",
384-
deny_msg="VNCAuth access denied (user not found)",
383+
allow=allow,
384+
allow_msg="VNCAuth access granted",
385+
deny_msg="VNCAuth access denied (passwd not found)",
385386
deny_reason="Invalid password",
386387
)
387388

kvmd/apps/vnc/server.py

+33-24
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import contextlib
2828

2929
import aiohttp
30+
import async_lru
3031

3132
from ...logging import get_logger
3233

@@ -55,9 +56,6 @@
5556
from .rfb.stream import rfb_format_remote
5657
from .rfb.errors import RfbError
5758

58-
from .vncauth import VncAuthKvmdCredentials
59-
from .vncauth import VncAuthManager
60-
6159
from .render import make_text_jpeg
6260

6361

@@ -89,14 +87,13 @@ def __init__( # pylint: disable=too-many-arguments,too-many-locals
8987
kvmd: KvmdClient,
9088
streamers: list[BaseStreamerClient],
9189

92-
vnc_credentials: dict[str, VncAuthKvmdCredentials],
90+
vncpasses: set[str],
9391
vencrypt: bool,
9492
none_auth_only: bool,
93+
9594
shared_params: _SharedParams,
9695
) -> None:
9796

98-
self.__vnc_credentials = vnc_credentials
99-
10097
super().__init__(
10198
reader=reader,
10299
writer=writer,
@@ -106,7 +103,7 @@ def __init__( # pylint: disable=too-many-arguments,too-many-locals
106103
x509_key_path=x509_key_path,
107104
scroll_rate=scroll_rate,
108105
allow_cut_after=allow_cut_after,
109-
vnc_passwds=list(vnc_credentials),
106+
vncpasses=vncpasses,
110107
vencrypt=vencrypt,
111108
none_auth_only=none_auth_only,
112109
**dataclasses.asdict(shared_params),
@@ -321,19 +318,17 @@ async def __fb_sender_task_loop(self) -> None: # pylint: disable=too-many-branc
321318
# =====
322319

323320
async def _authorize_userpass(self, user: str, passwd: str) -> bool:
324-
self.__kvmd_session = self.__kvmd.make_session(user, passwd)
325-
if (await self.__kvmd_session.auth.check()):
321+
self.__kvmd_session = self.__kvmd.make_session()
322+
if (await self.__kvmd_session.auth.check(user, passwd)):
326323
self.__stage1_authorized.set_passed()
327324
return True
328325
return False
329326

330-
async def _on_authorized_vnc_passwd(self, passwd: str) -> str:
331-
kc = self.__vnc_credentials[passwd]
332-
if (await self._authorize_userpass(kc.user, kc.passwd)):
333-
return kc.user
334-
return ""
327+
async def _on_authorized_vncpass(self) -> None:
328+
self.__kvmd_session = self.__kvmd.make_session()
329+
self.__stage1_authorized.set_passed()
335330

336-
async def _on_authorized_none(self) -> bool:
331+
async def _authorize_none(self) -> bool:
337332
return (await self._authorize_userpass("", ""))
338333

339334
# =====
@@ -461,6 +456,8 @@ def __init__( # pylint: disable=too-many-arguments,too-many-locals
461456
x509_cert_path: str,
462457
x509_key_path: str,
463458

459+
vncpass_enabled: bool,
460+
vncpass_path: str,
464461
vencrypt_enabled: bool,
465462

466463
desired_fps: int,
@@ -471,7 +468,6 @@ def __init__( # pylint: disable=too-many-arguments,too-many-locals
471468

472469
kvmd: KvmdClient,
473470
streamers: list[BaseStreamerClient],
474-
vnc_auth_manager: VncAuthManager,
475471
) -> None:
476472

477473
self.__host = network.get_listen_host(host)
@@ -481,7 +477,8 @@ def __init__( # pylint: disable=too-many-arguments,too-many-locals
481477
keymap_name = os.path.basename(keymap_path)
482478
symmap = build_symmap(keymap_path)
483479

484-
self.__vnc_auth_manager = vnc_auth_manager
480+
self.__vncpass_enabled = vncpass_enabled
481+
self.__vncpass_path = vncpass_path
485482

486483
shared_params = _SharedParams()
487484

@@ -508,8 +505,8 @@ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWrit
508505
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, timeout) # type: ignore
509506

510507
try:
511-
async with kvmd.make_session("", "") as kvmd_session:
512-
none_auth_only = await kvmd_session.auth.check()
508+
async with kvmd.make_session() as kvmd_session:
509+
none_auth_only = await kvmd_session.auth.check("", "")
513510
except (aiohttp.ClientError, asyncio.TimeoutError) as ex:
514511
logger.error("%s [entry]: Can't check KVMD auth mode: %s", remote, tools.efmt(ex))
515512
return
@@ -529,9 +526,9 @@ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWrit
529526
allow_cut_after=allow_cut_after,
530527
kvmd=kvmd,
531528
streamers=streamers,
532-
vnc_credentials=(await self.__vnc_auth_manager.read_credentials())[0],
533-
none_auth_only=none_auth_only,
529+
vncpasses=(await self.__read_vncpasses()),
534530
vencrypt=vencrypt_enabled,
531+
none_auth_only=none_auth_only,
535532
shared_params=shared_params,
536533
).run()
537534
except Exception:
@@ -542,9 +539,6 @@ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWrit
542539
self.__handle_client = handle_client
543540

544541
async def __inner_run(self) -> None:
545-
if not (await self.__vnc_auth_manager.read_credentials())[1]:
546-
raise SystemExit(1)
547-
548542
get_logger(0).info("Listening VNC on TCP [%s]:%d ...", self.__host, self.__port)
549543
(family, _, _, _, addr) = socket.getaddrinfo(self.__host, self.__port, type=socket.SOCK_STREAM)[0]
550544
with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as sock:
@@ -561,6 +555,21 @@ async def __inner_run(self) -> None:
561555
async with server:
562556
await server.serve_forever()
563557

558+
@async_lru.alru_cache(maxsize=1, ttl=1)
559+
async def __read_vncpasses(self) -> set[str]:
560+
if self.__vncpass_enabled:
561+
try:
562+
vncpasses: set[str] = set()
563+
for (_, line) in tools.passwds_splitted(await aiotools.read_file(self.__vncpass_path)):
564+
if " -> " in line: # Compatibility with old ipmipasswd file format
565+
line = line.split(" -> ", 1)[0]
566+
if len(line.strip()) > 0:
567+
vncpasses.add(line)
568+
return vncpasses
569+
except Exception:
570+
get_logger(0).exception("Unhandled exception while reading VNCAuth passwd file")
571+
return set()
572+
564573
def run(self) -> None:
565574
aiotools.run(self.__inner_run())
566575
get_logger().info("Bye-bye")

kvmd/apps/vnc/vncauth.py

-86
This file was deleted.

0 commit comments

Comments
 (0)