pikvm/pikvm#1204: Configurable global expiration policy

This commit is contained in:
Maxim Devaev 2025-02-09 00:40:48 +02:00
parent a7c3cdc1ea
commit 375a345820
4 changed files with 39 additions and 8 deletions

View File

@ -65,6 +65,7 @@ from ..validators.basic import valid_string_list
from ..validators.auth import valid_user
from ..validators.auth import valid_users_list
from ..validators.auth import valid_expire
from ..validators.os import valid_abs_path
from ..validators.os import valid_abs_file
@ -357,6 +358,7 @@ def _get_config_scheme() -> dict:
"auth": {
"enabled": Option(True, type=valid_bool),
"expire": Option(0, type=valid_expire),
"internal": {
"type": Option("htpasswd"),

View File

@ -76,6 +76,7 @@ def main(argv: (list[str] | None)=None) -> None:
KvmdServer(
auth_manager=AuthManager(
enabled=config.auth.enabled,
expire=config.auth.expire,
unauth_paths=([] if config.prometheus.auth.enabled else ["/export/prometheus/metrics"]),
internal_type=config.auth.internal.type,

View File

@ -43,15 +43,16 @@ class _Session:
expire_ts: int
def __post_init__(self) -> None:
assert self.user.strip()
assert self.user == self.user.strip()
assert self.user
assert self.expire_ts >= 0
class AuthManager:
class AuthManager: # pylint: disable=too-many-instance-attributes
def __init__(
self,
enabled: bool,
expire: int,
unauth_paths: list[str],
internal_type: str,
@ -68,6 +69,11 @@ class AuthManager:
if not enabled:
get_logger().warning("AUTHORIZATION IS DISABLED")
assert expire >= 0
self.__expire = expire
if expire > 0:
get_logger().warning("Maximum user session time is limited: %d seconds", expire)
self.__unauth_paths = frozenset(unauth_paths) # To speed up
for path in self.__unauth_paths:
get_logger().warning("Authorization is disabled for API %r", path)
@ -132,24 +138,46 @@ class AuthManager:
assert user
assert expire >= 0
assert self.__enabled
if (await self.authorize(user, passwd)):
token = self.__make_new_token()
session = _Session(
user=user,
expire_ts=(0 if expire <= 0 else (self.__get_now_ts() + expire)),
expire_ts=self.__make_expire_ts(expire),
)
self.__sessions[token] = session
get_logger().info("Logged in user %r (expire_ts=%d)", session.user, session.expire_ts)
return token
else:
return None
return None
def __make_new_token(self) -> str:
for _ in range(10):
token = secrets.token_hex(32)
if token not in self.__sessions:
return token
raise AssertionError("Can't generate new unique token")
raise RuntimeError("Can't generate new unique token")
def __make_expire_ts(self, expire: int) -> int:
assert expire >= 0
assert self.__expire >= 0
if expire == 0:
# The user requested infinite session: apply global expire.
# It will allow this (0) or set a limit.
expire = self.__expire
else:
# The user wants a limited session
if self.__expire > 0:
# If we have a global limit, override the user limit
assert expire > 0
expire = min(expire, self.__expire)
if expire > 0:
return (self.__get_now_ts() + expire)
assert expire == 0
return 0
def __get_now_ts(self) -> int:
return int(time.monotonic())
@ -171,12 +199,10 @@ class AuthManager:
if session is not None:
if session.expire_ts <= 0:
# Infinite session
assert session.user
return session.user
else:
# Limited session
if self.__get_now_ts() < session.expire_ts:
assert session.user
return session.user
else:
del self.__sessions[token]

View File

@ -61,6 +61,7 @@ async def _get_configured_manager(
manager = AuthManager(
enabled=True,
expire=0,
unauth_paths=unauth_paths,
internal_type="htpasswd",
@ -250,6 +251,7 @@ async def test_ok__disabled() -> None:
try:
manager = AuthManager(
enabled=False,
expire=0,
unauth_paths=[],
internal_type="foobar",