Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions infra/docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ services:
- "8080:8080"

py-genai-helper:
environment:
- KEYCLOAK_ISSUER_URL=http://localhost:8081/auth/realms/devops
- KEYCLOAK_JWKS_URL=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs
labels: !override
- "traefik.enable=true"
- "traefik.http.routers.py-genai-helper.entrypoints=web"
Expand Down
3 changes: 3 additions & 0 deletions infra/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ services:
container_name: py-genai-helper
env_file:
- ../services/py-genai-helper/.env
environment:
- KEYCLOAK_ISSUER_URL=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops
- KEYCLOAK_JWKS_URL=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs
expose:
- 5000
labels:
Expand Down
3 changes: 3 additions & 0 deletions infra/helm/team-devoops/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ services:
health: /health
stripPrefix: true
envFromSecret: genai-env
env:
KEYCLOAK_ISSUER_URL: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops"
KEYCLOAK_JWKS_URL: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs"
resources:
requests:
cpu: 100m
Expand Down
3 changes: 3 additions & 0 deletions services/py-genai-helper/app.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from flask import Flask, request

from auth import require_auth
from service import generate_rag_response, hello

app = Flask("genai-service")


@app.route("/hello")
@require_auth
def hello_world():
hello_message = hello()
return f"<p>{hello_message}</p>"
Expand All @@ -17,6 +19,7 @@ def health():


@app.route("/rag-response", methods=["POST"])
@require_auth
def rag_response():
# Get the json of the object. force=True ignores the stated MimeType
data = request.get_json(force=True) or {}
Expand Down
64 changes: 64 additions & 0 deletions services/py-genai-helper/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
from functools import wraps

import jwt
import requests
from flask import request

_jwks_cache: dict | None = None

KEYCLOAK_ISSUER_URL = os.environ.get(
"KEYCLOAK_ISSUER_URL",
"http://keycloak:8080/auth/realms/devops",
)
# Separate JWKS URL allows using the internal service name for fetching
# while the issuer URL matches the public hostname in the token's `iss` claim.
_JWKS_URL = os.environ.get(
"KEYCLOAK_JWKS_URL",
f"{KEYCLOAK_ISSUER_URL}/protocol/openid-connect/certs",
)


def _fetch_jwks() -> dict:
response = requests.get(_JWKS_URL, timeout=5)
response.raise_for_status()
return response.json()


def _get_signing_key(token: str) -> jwt.PyJWK:
global _jwks_cache
if _jwks_cache is None:
_jwks_cache = _fetch_jwks()
try:
return jwt.PyJWKClient(_JWKS_URL, jwks_data=_jwks_cache).get_signing_key_from_jwt(token)
except jwt.exceptions.PyJWKClientError:
# Key not found in cache — Keycloak may have rotated keys; refresh once.
_jwks_cache = _fetch_jwks()
return jwt.PyJWKClient(_JWKS_URL, jwks_data=_jwks_cache).get_signing_key_from_jwt(token)


def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
return {"error": "Missing or invalid Authorization header"}, 401

token = auth_header[len("Bearer ") :]
try:
signing_key = _get_signing_key(token)
jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
options={"verify_aud": False},
issuer=KEYCLOAK_ISSUER_URL,
)
except jwt.ExpiredSignatureError:
return {"error": "Token has expired"}, 401
except Exception:
return {"error": "Invalid token"}, 401

return f(*args, **kwargs)

return decorated
1 change: 1 addition & 0 deletions services/py-genai-helper/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pydantic-settings==2.14.1
pydantic_core==2.46.4
pypdf==5.6.0
python-dotenv==1.2.2
PyJWT[crypto]==2.10.1
PyYAML==6.0.3
regex==2026.5.9
requests==2.34.0
Expand Down
Loading