From ee6bcec3c5156f027ed7d7f40f208d1f1d216f40 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Wed, 31 Dec 2025 13:43:24 -0300 Subject: [PATCH] chat.ai: gate root with API key --- .../chat-ai-gateway-configmap.yaml | 78 +++++++++++++++++++ .../chat-ai-gateway-deployment.yaml | 69 ++++++++++++++++ .../chat-ai-gateway-service.yaml | 13 ++++ services/bstein-dev-home/ingress.yaml | 8 +- services/bstein-dev-home/kustomization.yaml | 3 + .../communication/atlasbot-deployment.yaml | 2 +- services/communication/chat-ai-keys.yaml | 9 --- services/communication/kustomization.yaml | 1 - 8 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 services/bstein-dev-home/chat-ai-gateway-configmap.yaml create mode 100644 services/bstein-dev-home/chat-ai-gateway-deployment.yaml create mode 100644 services/bstein-dev-home/chat-ai-gateway-service.yaml delete mode 100644 services/communication/chat-ai-keys.yaml diff --git a/services/bstein-dev-home/chat-ai-gateway-configmap.yaml b/services/bstein-dev-home/chat-ai-gateway-configmap.yaml new file mode 100644 index 0000000..17ed95b --- /dev/null +++ b/services/bstein-dev-home/chat-ai-gateway-configmap.yaml @@ -0,0 +1,78 @@ +# services/bstein-dev-home/chat-ai-gateway-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: chat-ai-gateway + namespace: bstein-dev-home +data: + gateway.py: | + import json + import os + from http.server import BaseHTTPRequestHandler, HTTPServer + from urllib import request, error + + UPSTREAM = os.environ.get("UPSTREAM_URL", "http://bstein-dev-home-backend/api/chat") + KEY_MATRIX = os.environ.get("CHAT_KEY_MATRIX", "") + KEY_HOMEPAGE = os.environ.get("CHAT_KEY_HOMEPAGE", "") + + ALLOWED = {k for k in (KEY_MATRIX, KEY_HOMEPAGE) if k} + + class Handler(BaseHTTPRequestHandler): + def _send_json(self, code: int, payload: dict): + body = json.dumps(payload).encode() + self.send_response(code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def do_GET(self): # noqa: N802 + if self.path in ("/healthz", "/"): + return self._send_json(200, {"ok": True}) + return self._send_json(404, {"error": "not_found"}) + + def do_POST(self): # noqa: N802 + if self.path != "/": + return self._send_json(404, {"error": "not_found"}) + + key = self.headers.get("x-api-key", "") + if not key or key not in ALLOWED: + return self._send_json(401, {"error": "unauthorized"}) + + length = int(self.headers.get("content-length", "0") or "0") + raw = self.rfile.read(length) if length else b"{}" + + try: + upstream_req = request.Request( + UPSTREAM, + data=raw, + headers={"Content-Type": "application/json"}, + method="POST", + ) + with request.urlopen(upstream_req, timeout=90) as resp: + data = resp.read() + self.send_response(resp.status) + for k, v in resp.headers.items(): + if k.lower() in ("content-length", "connection", "server", "date"): + continue + self.send_header(k, v) + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + except error.HTTPError as e: + data = e.read() if hasattr(e, "read") else b"" + self.send_response(e.code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + except Exception: + return self._send_json(502, {"error": "bad_gateway"}) + + def main(): + port = int(os.environ.get("PORT", "8080")) + httpd = HTTPServer(("0.0.0.0", port), Handler) + httpd.serve_forever() + + if __name__ == "__main__": + main() diff --git a/services/bstein-dev-home/chat-ai-gateway-deployment.yaml b/services/bstein-dev-home/chat-ai-gateway-deployment.yaml new file mode 100644 index 0000000..7ac6504 --- /dev/null +++ b/services/bstein-dev-home/chat-ai-gateway-deployment.yaml @@ -0,0 +1,69 @@ +# services/bstein-dev-home/chat-ai-gateway-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: chat-ai-gateway + namespace: bstein-dev-home +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app: chat-ai-gateway + template: + metadata: + labels: + app: chat-ai-gateway + spec: + nodeSelector: + kubernetes.io/arch: arm64 + node-role.kubernetes.io/worker: "true" + containers: + - name: gateway + image: python:3.11-slim + command: ["/bin/sh","-c"] + args: + - python /app/gateway.py + env: + - name: UPSTREAM_URL + value: http://bstein-dev-home-backend/api/chat + - name: CHAT_KEY_MATRIX + valueFrom: + secretKeyRef: + name: chat-ai-keys-runtime + key: matrix + - name: CHAT_KEY_HOMEPAGE + valueFrom: + secretKeyRef: + name: chat-ai-keys-runtime + key: homepage + ports: + - name: http + containerPort: 8080 + readinessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 2 + periodSeconds: 5 + livenessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + resources: + requests: + cpu: 20m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + volumeMounts: + - name: code + mountPath: /app/gateway.py + subPath: gateway.py + volumes: + - name: code + configMap: + name: chat-ai-gateway diff --git a/services/bstein-dev-home/chat-ai-gateway-service.yaml b/services/bstein-dev-home/chat-ai-gateway-service.yaml new file mode 100644 index 0000000..8a71d20 --- /dev/null +++ b/services/bstein-dev-home/chat-ai-gateway-service.yaml @@ -0,0 +1,13 @@ +# services/bstein-dev-home/chat-ai-gateway-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: chat-ai-gateway + namespace: bstein-dev-home +spec: + selector: + app: chat-ai-gateway + ports: + - name: http + port: 80 + targetPort: 8080 diff --git a/services/bstein-dev-home/ingress.yaml b/services/bstein-dev-home/ingress.yaml index 872a0df..1537c94 100644 --- a/services/bstein-dev-home/ingress.yaml +++ b/services/bstein-dev-home/ingress.yaml @@ -32,15 +32,9 @@ spec: - host: chat.ai.bstein.dev http: paths: - - path: /api - pathType: Prefix - backend: - service: - name: bstein-dev-home-backend - port: { number: 80 } - path: / pathType: Prefix backend: service: - name: bstein-dev-home-frontend + name: chat-ai-gateway port: { number: 80 } diff --git a/services/bstein-dev-home/kustomization.yaml b/services/bstein-dev-home/kustomization.yaml index e15af3e..99b9443 100644 --- a/services/bstein-dev-home/kustomization.yaml +++ b/services/bstein-dev-home/kustomization.yaml @@ -6,6 +6,9 @@ resources: - namespace.yaml - image.yaml - rbac.yaml + - chat-ai-gateway-configmap.yaml + - chat-ai-gateway-deployment.yaml + - chat-ai-gateway-service.yaml - frontend-deployment.yaml - frontend-service.yaml - backend-deployment.yaml diff --git a/services/communication/atlasbot-deployment.yaml b/services/communication/atlasbot-deployment.yaml index bd39ae7..55f1d2d 100644 --- a/services/communication/atlasbot-deployment.yaml +++ b/services/communication/atlasbot-deployment.yaml @@ -38,7 +38,7 @@ spec: - name: CHAT_API_KEY valueFrom: secretKeyRef: - name: chat-ai-keys + name: chat-ai-keys-runtime key: matrix - name: OLLAMA_URL value: https://chat.ai.bstein.dev/ diff --git a/services/communication/chat-ai-keys.yaml b/services/communication/chat-ai-keys.yaml deleted file mode 100644 index ac6c4e8..0000000 --- a/services/communication/chat-ai-keys.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# services/communication/chat-ai-keys.yaml -apiVersion: v1 -kind: Secret -metadata: - name: chat-ai-keys - namespace: communication -type: Opaque -stringData: - matrix: "3d9b1e5e80f146f2b3f6a9fbe01b7b77" diff --git a/services/communication/kustomization.yaml b/services/communication/kustomization.yaml index 9cd7f38..b579d4d 100644 --- a/services/communication/kustomization.yaml +++ b/services/communication/kustomization.yaml @@ -16,7 +16,6 @@ resources: - element-call-deployment.yaml - pin-othrys-job.yaml - guest-name-job.yaml - - chat-ai-keys.yaml - atlasbot-credentials.yaml - atlasbot-configmap.yaml - atlasbot-deployment.yaml