mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-29 00:51:53 +08:00
unix socket auth
This commit is contained in:
@@ -360,6 +360,11 @@ def _get_config_scheme() -> dict:
|
||||
"enabled": Option(True, type=valid_bool),
|
||||
"expire": Option(0, type=valid_expire),
|
||||
|
||||
"usc": {
|
||||
"users": Option([], type=valid_users_list), # PiKVM username has a same regex as a UNIX username
|
||||
"groups": Option([], type=valid_users_list), # groupname has a same regex as a username
|
||||
},
|
||||
|
||||
"internal": {
|
||||
"type": Option("htpasswd"),
|
||||
"force_users": Option([], type=valid_users_list),
|
||||
|
||||
@@ -77,6 +77,8 @@ def main(argv: (list[str] | None)=None) -> None:
|
||||
auth_manager=AuthManager(
|
||||
enabled=config.auth.enabled,
|
||||
expire=config.auth.expire,
|
||||
usc_users=config.auth.usc.users,
|
||||
usc_groups=config.auth.usc.groups,
|
||||
unauth_paths=([] if config.prometheus.auth.enabled else ["/export/prometheus/metrics"]),
|
||||
|
||||
int_type=config.auth.internal.type,
|
||||
|
||||
@@ -31,6 +31,7 @@ from ....htserver import HttpExposed
|
||||
from ....htserver import exposed_http
|
||||
from ....htserver import make_json_response
|
||||
from ....htserver import set_request_auth_info
|
||||
from ....htserver import get_request_unix_credentials
|
||||
|
||||
from ....validators.auth import valid_user
|
||||
from ....validators.auth import valid_passwd
|
||||
@@ -76,6 +77,14 @@ async def check_request_auth(auth_manager: AuthManager, exposed: HttpExposed, re
|
||||
raise ForbiddenError()
|
||||
return
|
||||
|
||||
if exposed.allow_usc:
|
||||
creds = get_request_unix_credentials(req)
|
||||
if creds is not None:
|
||||
user = auth_manager.check_unix_credentials(creds) # type: ignore
|
||||
if user:
|
||||
set_request_auth_info(req, f"{user}[{creds.uid}] (unix)")
|
||||
return
|
||||
|
||||
raise UnauthorizedError()
|
||||
|
||||
|
||||
@@ -85,7 +94,7 @@ class AuthApi:
|
||||
|
||||
# =====
|
||||
|
||||
@exposed_http("POST", "/auth/login", auth_required=False)
|
||||
@exposed_http("POST", "/auth/login", auth_required=False, allow_usc=False)
|
||||
async def __login_handler(self, req: Request) -> Response:
|
||||
if self.__auth_manager.is_auth_enabled():
|
||||
credentials = await req.post()
|
||||
@@ -99,13 +108,14 @@ class AuthApi:
|
||||
raise ForbiddenError()
|
||||
return make_json_response()
|
||||
|
||||
@exposed_http("POST", "/auth/logout")
|
||||
@exposed_http("POST", "/auth/logout", allow_usc=False)
|
||||
async def __logout_handler(self, req: Request) -> Response:
|
||||
if self.__auth_manager.is_auth_enabled():
|
||||
token = valid_auth_token(req.cookies.get(_COOKIE_AUTH_TOKEN, ""))
|
||||
self.__auth_manager.logout(token)
|
||||
return make_json_response()
|
||||
|
||||
@exposed_http("GET", "/auth/check")
|
||||
# XXX: This handle is used for access control so it should NEVER allow access by socket credentials
|
||||
@exposed_http("GET", "/auth/check", allow_usc=False)
|
||||
async def __check_handler(self, _: Request) -> Response:
|
||||
return make_json_response()
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import pwd
|
||||
import grp
|
||||
import dataclasses
|
||||
import time
|
||||
import datetime
|
||||
@@ -35,6 +37,7 @@ from ...plugins.auth import BaseAuthService
|
||||
from ...plugins.auth import get_auth_service_class
|
||||
|
||||
from ...htserver import HttpExposed
|
||||
from ...htserver import RequestUnixCredentials
|
||||
|
||||
|
||||
# =====
|
||||
@@ -49,11 +52,13 @@ class _Session:
|
||||
assert self.expire_ts >= 0
|
||||
|
||||
|
||||
class AuthManager: # pylint: disable=too-many-instance-attributes
|
||||
class AuthManager: # pylint: disable=too-many-arguments,too-many-instance-attributes
|
||||
def __init__(
|
||||
self,
|
||||
enabled: bool,
|
||||
expire: int,
|
||||
usc_users: list[str],
|
||||
usc_groups: list[str],
|
||||
unauth_paths: list[str],
|
||||
|
||||
int_type: str,
|
||||
@@ -78,9 +83,15 @@ class AuthManager: # pylint: disable=too-many-instance-attributes
|
||||
logger.info("Maximum user session time is limited: %s",
|
||||
self.__format_seconds(expire))
|
||||
|
||||
self.__usc_uids = self.__load_usc_uids(usc_users, usc_groups)
|
||||
if self.__usc_uids:
|
||||
logger.info("Unauth UNIX socket access is allowed for users: %s",
|
||||
list(self.__usc_uids.values()))
|
||||
|
||||
self.__unauth_paths = frozenset(unauth_paths) # To speed up
|
||||
for path in self.__unauth_paths:
|
||||
logger.warning("Authorization is disabled for API %r", path)
|
||||
if self.__unauth_paths:
|
||||
logger.info("Authorization is disabled for APIs: %s",
|
||||
list(self.__unauth_paths))
|
||||
|
||||
self.__int_service: (BaseAuthService | None) = None
|
||||
if enabled:
|
||||
@@ -244,3 +255,29 @@ class AuthManager: # pylint: disable=too-many-instance-attributes
|
||||
await self.__int_service.cleanup()
|
||||
if self.__ext_service:
|
||||
await self.__ext_service.cleanup()
|
||||
|
||||
# =====
|
||||
|
||||
def __load_usc_uids(self, users: list[str], groups: list[str]) -> dict[int, str]:
|
||||
uids: dict[int, str] = {}
|
||||
|
||||
pwds: dict[str, int] = {}
|
||||
for pw in pwd.getpwall():
|
||||
assert pw.pw_name == pw.pw_name.strip()
|
||||
assert pw.pw_name
|
||||
pwds[pw.pw_name] = pw.pw_uid
|
||||
if pw.pw_name in users:
|
||||
uids[pw.pw_uid] = pw.pw_name
|
||||
|
||||
for gr in grp.getgrall():
|
||||
if gr.gr_name in groups:
|
||||
for member in gr.gr_mem:
|
||||
if member in pwds:
|
||||
uid = pwds[member]
|
||||
uids[uid] = member
|
||||
|
||||
return uids
|
||||
|
||||
def check_unix_credentials(self, creds: RequestUnixCredentials) -> (str | None):
|
||||
assert self.__enabled
|
||||
return self.__usc_uids.get(creds.uid)
|
||||
|
||||
Reference in New Issue
Block a user