fix(auth): keep OIDC startup warnings off the optional crypto path (#3825)
This commit is contained in:
parent
f7a9c98810
commit
3b26cab859
4 changed files with 52 additions and 37 deletions
45
api/auth.py
45
api/auth.py
|
|
@ -16,7 +16,7 @@ import threading
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from api.config import STATE_DIR, load_settings
|
from api.config import STATE_DIR, get_config, load_settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -455,6 +455,49 @@ def is_oidc_auth_enabled() -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_oidc_startup_warning() -> str | None:
|
||||||
|
"""Return a startup warning when OIDC auth is only partially configured."""
|
||||||
|
try:
|
||||||
|
cfg = get_config()
|
||||||
|
raw = cfg.get("webui_oidc") if isinstance(cfg, dict) else {}
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raw = {}
|
||||||
|
except Exception:
|
||||||
|
logger.debug("Failed to read webui_oidc config", exc_info=True)
|
||||||
|
raw = {}
|
||||||
|
|
||||||
|
def pick(name: str, env_name: str) -> str:
|
||||||
|
env_value = os.getenv(env_name)
|
||||||
|
value = env_value if env_value is not None else raw.get(name)
|
||||||
|
return str(value or "").strip()
|
||||||
|
|
||||||
|
issuer = bool(pick("issuer", "HERMES_WEBUI_OIDC_ISSUER"))
|
||||||
|
client_id = bool(pick("client_id", "HERMES_WEBUI_OIDC_CLIENT_ID"))
|
||||||
|
allow_claim = bool(pick("allow_claim", "HERMES_WEBUI_OIDC_ALLOW_CLAIM"))
|
||||||
|
allow_values = bool(pick("allow_values", "HERMES_WEBUI_OIDC_ALLOW_VALUES"))
|
||||||
|
|
||||||
|
if not any((issuer, client_id, allow_claim, allow_values)):
|
||||||
|
return None
|
||||||
|
if issuer and client_id and allow_claim and allow_values:
|
||||||
|
return None
|
||||||
|
|
||||||
|
missing = []
|
||||||
|
if not issuer:
|
||||||
|
missing.append("issuer")
|
||||||
|
if not client_id:
|
||||||
|
missing.append("client_id")
|
||||||
|
if not allow_claim:
|
||||||
|
missing.append("allow_claim")
|
||||||
|
if not allow_values:
|
||||||
|
missing.append("allow_values")
|
||||||
|
|
||||||
|
joined = ", ".join(missing)
|
||||||
|
return (
|
||||||
|
"Native OIDC login is only partially configured; missing "
|
||||||
|
f"{joined}. The WebUI will not enable OIDC auth until all four fields are set."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_auth_enabled() -> bool:
|
def is_auth_enabled() -> bool:
|
||||||
"""True if password auth, passkeys, or OIDC login is configured."""
|
"""True if password auth, passkeys, or OIDC login is configured."""
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -54,34 +54,6 @@ def is_oidc_enabled() -> bool:
|
||||||
and cfg.get("allow_values")
|
and cfg.get("allow_values")
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_oidc_startup_warning() -> str | None:
|
|
||||||
cfg = _resolve_oidc_config()
|
|
||||||
issuer = bool(cfg.get("issuer"))
|
|
||||||
client_id = bool(cfg.get("client_id"))
|
|
||||||
allow_claim = bool(cfg.get("allow_claim"))
|
|
||||||
allow_values = bool(cfg.get("allow_values"))
|
|
||||||
|
|
||||||
if not any((issuer, client_id, allow_claim, allow_values)):
|
|
||||||
return None
|
|
||||||
if issuer and client_id and allow_claim and allow_values:
|
|
||||||
return None
|
|
||||||
|
|
||||||
missing = []
|
|
||||||
if not issuer:
|
|
||||||
missing.append("issuer")
|
|
||||||
if not client_id:
|
|
||||||
missing.append("client_id")
|
|
||||||
if not allow_claim:
|
|
||||||
missing.append("allow_claim")
|
|
||||||
if not allow_values:
|
|
||||||
missing.append("allow_values")
|
|
||||||
|
|
||||||
joined = ", ".join(missing)
|
|
||||||
return (
|
|
||||||
"Native OIDC login is only partially configured; missing "
|
|
||||||
f"{joined}. The WebUI will not enable OIDC auth until all four fields are set."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_authorization_redirect(
|
def build_authorization_redirect(
|
||||||
request_base_url: str,
|
request_base_url: str,
|
||||||
|
|
|
||||||
|
|
@ -584,8 +584,8 @@ def main() -> None:
|
||||||
if within_container:
|
if within_container:
|
||||||
print('[ok] Running within container.', flush=True)
|
print('[ok] Running within container.', flush=True)
|
||||||
|
|
||||||
from api.auth import is_auth_enabled
|
# Security: warn if binding non-loopback without authentication
|
||||||
from api.auth_oidc import get_oidc_startup_warning
|
from api.auth import get_oidc_startup_warning, is_auth_enabled
|
||||||
if HOST not in ('127.0.0.1', '::1', 'localhost') and not is_auth_enabled():
|
if HOST not in ('127.0.0.1', '::1', 'localhost') and not is_auth_enabled():
|
||||||
print(f'[!!] WARNING: Binding to {HOST} with NO PASSWORD SET.', flush=True)
|
print(f'[!!] WARNING: Binding to {HOST} with NO PASSWORD SET.', flush=True)
|
||||||
print(f' Anyone on the network can access your filesystem and agent.', flush=True)
|
print(f' Anyone on the network can access your filesystem and agent.', flush=True)
|
||||||
|
|
|
||||||
|
|
@ -246,10 +246,10 @@ def test_oidc_enablement_requires_explicit_allowlist(monkeypatch):
|
||||||
assert auth_oidc.is_oidc_enabled() is False
|
assert auth_oidc.is_oidc_enabled() is False
|
||||||
|
|
||||||
def test_oidc_startup_warning_flags_partial_config(monkeypatch):
|
def test_oidc_startup_warning_flags_partial_config(monkeypatch):
|
||||||
import api.auth_oidc as auth_oidc
|
import api.auth as auth
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
auth_oidc,
|
auth,
|
||||||
"get_config",
|
"get_config",
|
||||||
lambda: {
|
lambda: {
|
||||||
"webui_oidc": {
|
"webui_oidc": {
|
||||||
|
|
@ -259,16 +259,16 @@ def test_oidc_startup_warning_flags_partial_config(monkeypatch):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
warning = auth_oidc.get_oidc_startup_warning()
|
warning = auth.get_oidc_startup_warning()
|
||||||
assert warning is not None
|
assert warning is not None
|
||||||
assert "allow_claim" in warning
|
assert "allow_claim" in warning
|
||||||
assert "allow_values" in warning
|
assert "allow_values" in warning
|
||||||
|
|
||||||
def test_oidc_startup_warning_ignores_complete_config(monkeypatch):
|
def test_oidc_startup_warning_ignores_complete_config(monkeypatch):
|
||||||
import api.auth_oidc as auth_oidc
|
import api.auth as auth
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
auth_oidc,
|
auth,
|
||||||
"get_config",
|
"get_config",
|
||||||
lambda: {
|
lambda: {
|
||||||
"webui_oidc": {
|
"webui_oidc": {
|
||||||
|
|
@ -280,7 +280,7 @@ def test_oidc_startup_warning_ignores_complete_config(monkeypatch):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert auth_oidc.get_oidc_startup_warning() is None
|
assert auth.get_oidc_startup_warning() is None
|
||||||
|
|
||||||
def test_validate_id_token_rejects_mismatched_jwk_key_family(monkeypatch):
|
def test_validate_id_token_rejects_mismatched_jwk_key_family(monkeypatch):
|
||||||
import api.auth_oidc as auth_oidc
|
import api.auth_oidc as auth_oidc
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue