summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Devaev <[email protected]>2023-08-18 00:21:07 +0300
committerMaxim Devaev <[email protected]>2023-08-18 00:21:07 +0300
commit61ce81ab6401046a02aa3eb048af6deed004f840 (patch)
tree35cc2e044e064318269152426f457cc98b938a69
parent32560563dc41f19633944e18cf16fef05b88c407 (diff)
pikvm/pikvm#1069: added option to disable auth on prometheus api
-rw-r--r--kvmd/apps/__init__.py6
-rw-r--r--kvmd/apps/kvmd/__init__.py1
-rw-r--r--kvmd/apps/kvmd/api/auth.py2
-rw-r--r--kvmd/apps/kvmd/auth.py14
-rw-r--r--testenv/tests/apps/kvmd/test_auth.py44
5 files changed, 64 insertions, 3 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py
index 4b840e70..4bd8a60d 100644
--- a/kvmd/apps/__init__.py
+++ b/kvmd/apps/__init__.py
@@ -397,6 +397,12 @@ def _get_config_scheme() -> dict:
"enabled": Option(True, type=valid_bool),
},
+ "prometheus": {
+ "auth": {
+ "enabled": Option(True, type=valid_bool),
+ },
+ },
+
"hid": {
"type": Option("", type=valid_stripped_string_not_empty),
diff --git a/kvmd/apps/kvmd/__init__.py b/kvmd/apps/kvmd/__init__.py
index f02085e1..2d0219db 100644
--- a/kvmd/apps/kvmd/__init__.py
+++ b/kvmd/apps/kvmd/__init__.py
@@ -75,6 +75,7 @@ def main(argv: (list[str] | None)=None) -> None:
KvmdServer(
auth_manager=AuthManager(
enabled=config.auth.enabled,
+ unauth_paths=([] if config.prometheus.auth.enabled else ["/export/prometheus/metrics"]),
internal_type=config.auth.internal.type,
internal_kwargs=config.auth.internal._unpack(ignore=["type", "force_users"]),
diff --git a/kvmd/apps/kvmd/api/auth.py b/kvmd/apps/kvmd/api/auth.py
index 7d2fad36..5f2e847e 100644
--- a/kvmd/apps/kvmd/api/auth.py
+++ b/kvmd/apps/kvmd/api/auth.py
@@ -44,7 +44,7 @@ _COOKIE_AUTH_TOKEN = "auth_token"
async def check_request_auth(auth_manager: AuthManager, exposed: HttpExposed, request: Request) -> None:
- if exposed.auth_required and auth_manager.is_auth_enabled():
+ if auth_manager.is_auth_required(exposed):
user = request.headers.get("X-KVMD-User", "")
if user:
user = valid_user(user)
diff --git a/kvmd/apps/kvmd/auth.py b/kvmd/apps/kvmd/auth.py
index 21af1a93..8b0d104d 100644
--- a/kvmd/apps/kvmd/auth.py
+++ b/kvmd/apps/kvmd/auth.py
@@ -30,12 +30,15 @@ from ... import aiotools
from ...plugins.auth import BaseAuthService
from ...plugins.auth import get_auth_service_class
+from ...htserver import HttpExposed
+
# =====
class AuthManager:
def __init__(
self,
enabled: bool,
+ unauth_paths: list[str],
internal_type: str,
internal_kwargs: dict,
@@ -51,6 +54,10 @@ class AuthManager:
if not enabled:
get_logger().warning("AUTHORIZATION IS DISABLED")
+ 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)
+
self.__internal_service: (BaseAuthService | None) = None
if enabled:
self.__internal_service = get_auth_service_class(internal_type)(**internal_kwargs)
@@ -70,6 +77,13 @@ class AuthManager:
def is_auth_enabled(self) -> bool:
return self.__enabled
+ def is_auth_required(self, exposed: HttpExposed) -> bool:
+ return (
+ self.is_auth_enabled()
+ and exposed.auth_required
+ and exposed.path not in self.__unauth_paths
+ )
+
async def authorize(self, user: str, passwd: str) -> bool:
assert user == user.strip()
assert user
diff --git a/testenv/tests/apps/kvmd/test_auth.py b/testenv/tests/apps/kvmd/test_auth.py
index a203e016..12be04a1 100644
--- a/testenv/tests/apps/kvmd/test_auth.py
+++ b/testenv/tests/apps/kvmd/test_auth.py
@@ -35,8 +35,15 @@ from kvmd.apps.kvmd.auth import AuthManager
from kvmd.plugins.auth import get_auth_service_class
+from kvmd.htserver import HttpExposed
+
# =====
+_E_AUTH = HttpExposed("GET", "/foo_auth", True, (lambda: None))
+_E_UNAUTH = HttpExposed("GET", "/bar_unauth", True, (lambda: None))
+_E_FREE = HttpExposed("GET", "/baz_free", False, (lambda: None))
+
+
def _make_service_kwargs(path: str) -> dict:
cls = get_auth_service_class("htpasswd")
scheme = cls.get_plugin_options()
@@ -45,6 +52,7 @@ def _make_service_kwargs(path: str) -> dict:
@contextlib.asynccontextmanager
async def _get_configured_manager(
+ unauth_paths: list[str],
internal_path: str,
external_path: str="",
force_internal_users: (list[str] | None)=None,
@@ -52,6 +60,7 @@ async def _get_configured_manager(
manager = AuthManager(
enabled=True,
+ unauth_paths=unauth_paths,
internal_type="htpasswd",
internal_kwargs=_make_service_kwargs(internal_path),
@@ -78,8 +87,11 @@ async def test_ok__internal(tmpdir) -> None: # type: ignore
htpasswd.set_password("admin", "pass")
htpasswd.save()
- async with _get_configured_manager(path) as manager:
+ async with _get_configured_manager([], path) as manager:
assert manager.is_auth_enabled()
+ assert manager.is_auth_required(_E_AUTH)
+ assert manager.is_auth_required(_E_UNAUTH)
+ assert not manager.is_auth_required(_E_FREE)
assert manager.check("xxx") is None
manager.logout("xxx")
@@ -118,8 +130,11 @@ async def test_ok__external(tmpdir) -> None: # type: ignore
htpasswd2.set_password("user", "foobar")
htpasswd2.save()
- async with _get_configured_manager(path1, path2, ["admin"]) as manager:
+ async with _get_configured_manager([], path1, path2, ["admin"]) as manager:
assert manager.is_auth_enabled()
+ assert manager.is_auth_required(_E_AUTH)
+ assert manager.is_auth_required(_E_UNAUTH)
+ assert not manager.is_auth_required(_E_FREE)
assert (await manager.login("local", "foobar")) is None
assert (await manager.login("admin", "pass2")) is None
@@ -140,10 +155,32 @@ async def test_ok__external(tmpdir) -> None: # type: ignore
@pytest.mark.asyncio
+async def test_ok__unauth(tmpdir) -> None: # type: ignore
+ path = os.path.abspath(str(tmpdir.join("htpasswd")))
+
+ htpasswd = passlib.apache.HtpasswdFile(path, new=True)
+ htpasswd.set_password("admin", "pass")
+ htpasswd.save()
+
+ async with _get_configured_manager([
+ "", " ",
+ "foo_auth", "/foo_auth ", " /foo_auth",
+ "/foo_authx", "/foo_auth/", "/foo_auth/x",
+ "/bar_unauth", # Only this one is matching
+ ], path) as manager:
+
+ assert manager.is_auth_enabled()
+ assert manager.is_auth_required(_E_AUTH)
+ assert not manager.is_auth_required(_E_UNAUTH)
+ assert not manager.is_auth_required(_E_FREE)
+
+
async def test_ok__disabled() -> None:
try:
manager = AuthManager(
enabled=False,
+ unauth_paths=[],
internal_type="foobar",
internal_kwargs={},
@@ -156,6 +193,9 @@ async def test_ok__disabled() -> None:
)
assert not manager.is_auth_enabled()
+ assert not manager.is_auth_required(_E_AUTH)
+ assert not manager.is_auth_required(_E_UNAUTH)
+ assert not manager.is_auth_required(_E_FREE)
with pytest.raises(AssertionError):
await manager.authorize("admin", "admin")