fix(hermes): report consumer access denial
This commit is contained in:
parent
4fe6dba491
commit
fa21a760ef
@ -39,38 +39,76 @@ source = source.replace(attachBefore, attachAfter);
|
||||
fs.writeFileSync(path, source);
|
||||
NODE
|
||||
|
||||
# The upstream self-hosted OIDC plugin authenticates users but deliberately
|
||||
# treats the dashboard as one shared workstation. Allow a deployment to narrow
|
||||
# that workstation to an explicit OIDC subject without changing default
|
||||
# behavior for the operator instance.
|
||||
# The upstream OIDC gate authenticates users but deliberately treats the
|
||||
# dashboard as one shared workstation. Allow a deployment to narrow that
|
||||
# workstation to explicit OIDC subjects. Enforce this after normal provider
|
||||
# verification so a denied account is a 403, not a misleading provider 503.
|
||||
RUN python - <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
path = Path("/opt/hermes/plugins/dashboard_auth/self_hosted/__init__.py")
|
||||
path = Path("/opt/hermes/hermes_cli/dashboard_auth/middleware.py")
|
||||
source = path.read_text()
|
||||
before = ''' if not user_id:
|
||||
raise ProviderError("ID token missing 'sub' (user_id) claim")
|
||||
helper_before = '''def _client_ip(request: Request) -> str:
|
||||
fwd = request.headers.get("x-forwarded-for", "")
|
||||
if fwd:
|
||||
return fwd.split(",")[0].strip()
|
||||
return request.client.host if request.client else ""
|
||||
|
||||
|
||||
email = str(claims.get("email", "") or "")
|
||||
'''
|
||||
after = ''' if not user_id:
|
||||
raise ProviderError("ID token missing 'sub' (user_id) claim")
|
||||
helper_after = helper_before + '''def _dashboard_user_allowed(session) -> bool:
|
||||
"""Apply an optional deployment-level OIDC-subject allowlist."""
|
||||
import os
|
||||
|
||||
allowed_user_ids = {
|
||||
allowed = {
|
||||
value.strip()
|
||||
for value in os.environ.get(
|
||||
"HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS", ""
|
||||
).split(",")
|
||||
if value.strip()
|
||||
}
|
||||
if allowed_user_ids and user_id not in allowed_user_ids:
|
||||
raise ProviderError("This account is not authorized for this dashboard")
|
||||
return not allowed or session.user_id in allowed
|
||||
|
||||
|
||||
def _user_forbidden_response() -> Response:
|
||||
"""Return an authorization failure without exposing identities."""
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": "forbidden",
|
||||
"detail": "This Atlas account is not authorized for this dashboard.",
|
||||
},
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
|
||||
email = str(claims.get("email", "") or "")
|
||||
'''
|
||||
if before not in source:
|
||||
raise SystemExit("Hermes self-hosted OIDC patch context changed")
|
||||
path.write_text(source.replace(before, after))
|
||||
refresh_before = ''' new_session, refreshing_provider = refreshed
|
||||
request.state.session = new_session
|
||||
response = await call_next(request)
|
||||
'''
|
||||
refresh_after = ''' new_session, refreshing_provider = refreshed
|
||||
if not _dashboard_user_allowed(new_session):
|
||||
return _user_forbidden_response()
|
||||
request.state.session = new_session
|
||||
response = await call_next(request)
|
||||
'''
|
||||
final_before = ''' request.state.session = session
|
||||
return await call_next(request)
|
||||
'''
|
||||
final_after = ''' if not _dashboard_user_allowed(session):
|
||||
return _user_forbidden_response()
|
||||
request.state.session = session
|
||||
return await call_next(request)
|
||||
'''
|
||||
for before, after, label in (
|
||||
(helper_before, helper_after, "allowlist helper"),
|
||||
(refresh_before, refresh_after, "refreshed session"),
|
||||
(final_before, final_after, "verified session"),
|
||||
):
|
||||
if before not in source:
|
||||
raise SystemExit(f"Hermes dashboard auth {label} patch context changed")
|
||||
source = source.replace(before, after, 1)
|
||||
path.write_text(source)
|
||||
PY
|
||||
|
||||
COPY dockerfiles/hermes-session-migrate.py /opt/hermes/bin/hermes-session-migrate
|
||||
@ -80,5 +118,5 @@ RUN cd /opt/hermes/web \
|
||||
&& grep -Fq 'if (unmounting) return;' src/pages/ChatPage.tsx \
|
||||
&& grep -Fq 'resume:${resumeParam}' src/pages/ChatPage.tsx \
|
||||
&& grep -Fq 'HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS' \
|
||||
/opt/hermes/plugins/dashboard_auth/self_hosted/__init__.py \
|
||||
/opt/hermes/hermes_cli/dashboard_auth/middleware.py \
|
||||
&& chmod 0755 /opt/hermes/bin/hermes-session-migrate
|
||||
|
||||
@ -77,7 +77,7 @@ spec:
|
||||
- rpi4
|
||||
initContainers:
|
||||
- name: migrate-user-sessions
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:ba3e81a1cefcc178729891a7af31269e65b94a72d880f0cb144d3ddbbceaeaba
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /opt/hermes/bin/hermes-session-migrate
|
||||
@ -174,7 +174,7 @@ spec:
|
||||
memory: 64Mi
|
||||
containers:
|
||||
- name: hermes-chat
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:ba3e81a1cefcc178729891a7af31269e65b94a72d880f0cb144d3ddbbceaeaba
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- gateway
|
||||
|
||||
@ -134,7 +134,7 @@ spec:
|
||||
memory: 64Mi
|
||||
containers:
|
||||
- name: hermes
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:ba3e81a1cefcc178729891a7af31269e65b94a72d880f0cb144d3ddbbceaeaba
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- gateway
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user