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);
|
fs.writeFileSync(path, source);
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
# The upstream self-hosted OIDC plugin authenticates users but deliberately
|
# The upstream OIDC gate authenticates users but deliberately treats the
|
||||||
# treats the dashboard as one shared workstation. Allow a deployment to narrow
|
# dashboard as one shared workstation. Allow a deployment to narrow that
|
||||||
# that workstation to an explicit OIDC subject without changing default
|
# workstation to explicit OIDC subjects. Enforce this after normal provider
|
||||||
# behavior for the operator instance.
|
# verification so a denied account is a 403, not a misleading provider 503.
|
||||||
RUN python - <<'PY'
|
RUN python - <<'PY'
|
||||||
from pathlib import Path
|
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()
|
source = path.read_text()
|
||||||
before = ''' if not user_id:
|
helper_before = '''def _client_ip(request: Request) -> str:
|
||||||
raise ProviderError("ID token missing 'sub' (user_id) claim")
|
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:
|
helper_after = helper_before + '''def _dashboard_user_allowed(session) -> bool:
|
||||||
raise ProviderError("ID token missing 'sub' (user_id) claim")
|
"""Apply an optional deployment-level OIDC-subject allowlist."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
allowed = {
|
||||||
|
value.strip()
|
||||||
|
for value in os.environ.get(
|
||||||
|
"HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS", ""
|
||||||
|
).split(",")
|
||||||
|
if value.strip()
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
allowed_user_ids = {
|
|
||||||
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")
|
|
||||||
|
|
||||||
email = str(claims.get("email", "") or "")
|
|
||||||
'''
|
'''
|
||||||
if before not in source:
|
refresh_before = ''' new_session, refreshing_provider = refreshed
|
||||||
raise SystemExit("Hermes self-hosted OIDC patch context changed")
|
request.state.session = new_session
|
||||||
path.write_text(source.replace(before, after))
|
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
|
PY
|
||||||
|
|
||||||
COPY dockerfiles/hermes-session-migrate.py /opt/hermes/bin/hermes-session-migrate
|
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 'if (unmounting) return;' src/pages/ChatPage.tsx \
|
||||||
&& grep -Fq 'resume:${resumeParam}' src/pages/ChatPage.tsx \
|
&& grep -Fq 'resume:${resumeParam}' src/pages/ChatPage.tsx \
|
||||||
&& grep -Fq 'HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS' \
|
&& 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
|
&& chmod 0755 /opt/hermes/bin/hermes-session-migrate
|
||||||
|
|||||||
@ -77,7 +77,7 @@ spec:
|
|||||||
- rpi4
|
- rpi4
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: migrate-user-sessions
|
- name: migrate-user-sessions
|
||||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:ba3e81a1cefcc178729891a7af31269e65b94a72d880f0cb144d3ddbbceaeaba
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- /opt/hermes/bin/hermes-session-migrate
|
- /opt/hermes/bin/hermes-session-migrate
|
||||||
@ -174,7 +174,7 @@ spec:
|
|||||||
memory: 64Mi
|
memory: 64Mi
|
||||||
containers:
|
containers:
|
||||||
- name: hermes-chat
|
- name: hermes-chat
|
||||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:ba3e81a1cefcc178729891a7af31269e65b94a72d880f0cb144d3ddbbceaeaba
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
args:
|
args:
|
||||||
- gateway
|
- gateway
|
||||||
|
|||||||
@ -134,7 +134,7 @@ spec:
|
|||||||
memory: 64Mi
|
memory: 64Mi
|
||||||
containers:
|
containers:
|
||||||
- name: hermes
|
- name: hermes
|
||||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:ba3e81a1cefcc178729891a7af31269e65b94a72d880f0cb144d3ddbbceaeaba
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
args:
|
args:
|
||||||
- gateway
|
- gateway
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user