🔐 Auth: інтеграція JWT в Memory Service + конфігурації

- Опціональна JWT auth в Memory Service endpoints
- get_current_service_optional для backward compatibility
- NATS auth config (nkeys) - шаблони
- Qdrant auth config (API keys) - шаблони
- Тестовий скрипт для повного потоку

TODO: Генерація реальних JWT/ключів та застосування конфігів
This commit is contained in:
Apple
2026-01-10 10:46:25 -08:00
parent 6c426bc274
commit 38cb96dd68
5 changed files with 193 additions and 5 deletions

View File

@@ -0,0 +1,31 @@
---
# Qdrant Auth Configuration (API keys)
apiVersion: v1
kind: Secret
metadata:
name: qdrant-api-keys
namespace: qdrant
type: Opaque
stringData:
# TODO: Замінити на реальні API ключі (згенеровані через generate-keys.sh)
memory-service-key: "change-me-memory-service"
worker-daemon-key: "change-me-worker-daemon"
matrix-gateway-key: "change-me-matrix-gateway"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: qdrant-auth-config
namespace: qdrant
data:
# Qdrant config з API key auth
qdrant-config.yaml: |
service:
api_key: ${QDRANT_API_KEY} # З Secret
enable_cors: true
cluster:
enabled: false # Single node для початку
storage:
storage_path: /qdrant/storage

View File

@@ -0,0 +1,39 @@
---
# NATS Auth Configuration (nkeys)
# Використання: після генерації operator JWT через generate-operator.sh
apiVersion: v1
kind: Secret
metadata:
name: nats-operator-jwt
namespace: nats
type: Opaque
stringData:
# TODO: Замінити на реальний operator JWT
operator.jwt: |
eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ...
system-account.jwt: |
eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ...
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nats-auth-config
namespace: nats
data:
# Приклад конфігурації з auth (після генерації JWT)
nats-auth.conf: |
port: 4222
http_port: 8222
server_name: POD_NAME_PLACEHOLDER
# Operator JWT
operator: /etc/nats/jwt/operator.jwt
system_account: SYSTEM
resolver: MEMORY
jetstream {
store_dir: /data/jetstream
max_mem_store: 2G
max_file_store: 50G
}

View File

@@ -0,0 +1,93 @@
#!/bin/bash
# Тестування повного потоку: Matrix → Gateway → NATS → Worker → Memory Service
set -e
echo "🧪 Тестування повного потоку..."
echo ""
# Кольори для виводу
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Перевірка компонентів
echo "=== Перевірка компонентів ==="
# 1. NATS
echo -n "NATS JetStream: "
if kubectl get pods -n nats -l app=nats --field-selector=status.phase=Running 2>/dev/null | grep -q nats; then
echo -e "${GREEN}✅ Running${NC}"
else
echo -e "${RED}❌ Not running${NC}"
exit 1
fi
# 2. Memory Service
echo -n "Memory Service: "
if kubectl get pods -n daarion -l app=memory-service --field-selector=status.phase=Running 2>/dev/null | grep -q memory-service; then
echo -e "${GREEN}✅ Running${NC}"
else
echo -e "${YELLOW}⚠️ Not running (може бути нормально)${NC}"
fi
# 3. Qdrant
echo -n "Qdrant: "
if kubectl get pods -n qdrant -l app=qdrant --field-selector=status.phase=Running 2>/dev/null | grep -q qdrant; then
echo -e "${GREEN}✅ Running${NC}"
else
echo -e "${YELLOW}⚠️ Not running (може бути нормально)${NC}"
fi
echo ""
echo "=== Перевірка NATS streams ==="
NATS_POD=$(kubectl get pods -n nats -l app=nats -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -n "$NATS_POD" ]; then
kubectl exec -n nats "$NATS_POD" -- wget -qO- http://localhost:8222/jsz 2>/dev/null | python3 -m json.tool 2>/dev/null | grep -E '"streams"|"name"' | head -10 || echo "Streams не знайдено"
else
echo "NATS pod не знайдено"
fi
echo ""
echo "=== Тест створення job ==="
echo "Створюю тестовий job через Python..."
cat << 'PYEOF' | python3
import asyncio
import json
from nats.js import api
from nats.aio.client import Client as NATS
async def test_job():
nc = NATS()
try:
await nc.connect("nats://nats-client.nats:4222")
js = nc.jetstream()
# Тестовий job
job = {
"job_id": "test-001",
"idempotency_key": "sha256:test",
"type": "embed",
"priority": "online",
"input": {
"text": ["Тестовий текст для embedding"],
"model": "cohere/embed-multilingual-v3.0",
"dims": 1024
}
}
# Публікація
ack = await js.publish("mm.embed.online", json.dumps(job).encode())
print(f"✅ Job опубліковано: seq={ack.seq}")
await nc.close()
except Exception as e:
print(f"❌ Помилка: {e}")
asyncio.run(test_job())
PYEOF
echo ""
echo "✅ Тестування завершено!"

View File

@@ -5,7 +5,7 @@ JWT Authentication для Memory Service
import os
import jwt
import time
from typing import Optional
from typing import Optional, Union
from fastapi import HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from app.config import get_settings
@@ -42,8 +42,21 @@ def verify_jwt_token(token: str) -> dict:
raise HTTPException(status_code=401, detail="Invalid token")
async def get_current_service_optional(
credentials: Optional[HTTPAuthorizationCredentials] = Security(security, auto_error=False)
) -> Optional[dict]:
"""Dependency для отримання поточного сервісу з JWT (опціонально)"""
if not credentials:
return None
token = credentials.credentials
try:
payload = verify_jwt_token(token)
return payload
except HTTPException:
return None
async def get_current_service(credentials: HTTPAuthorizationCredentials = Security(security)) -> dict:
"""Dependency для отримання поточного сервісу з JWT"""
"""Dependency для отримання поточного сервісу з JWT (обов'язково)"""
token = credentials.credentials
payload = verify_jwt_token(token)
return payload

View File

@@ -8,6 +8,7 @@ DAARION Memory Service - FastAPI Application
"""
from contextlib import asynccontextmanager
from typing import List, Optional
from fastapi import Depends
from uuid import UUID
import structlog
from fastapi import FastAPI, HTTPException, Query
@@ -23,6 +24,7 @@ from .models import (
)
from .vector_store import vector_store
from .database import db
from .auth import get_current_service, get_current_service_optional
logger = structlog.get_logger()
settings = get_settings()
@@ -76,8 +78,12 @@ async def health():
# ============================================================================
@app.post("/threads", response_model=ThreadResponse)
async def create_thread(request: CreateThreadRequest):
async def create_thread(
request: CreateThreadRequest,
service: Optional[dict] = Depends(get_current_service_optional)
):
"""Create new conversation thread"""
# Auth опціональний: якщо JWT надано, перевіряємо; якщо ні - дозволяємо (dev режим)
thread = await db.create_thread(
org_id=request.org_id,
user_id=request.user_id,
@@ -123,7 +129,10 @@ async def list_threads(
# ============================================================================
@app.post("/events", response_model=EventResponse)
async def add_event(request: AddEventRequest):
async def add_event(
request: AddEventRequest,
service: Optional[dict] = Depends(get_current_service_optional)
):
"""Add event to conversation (message, tool call, etc.)"""
event = await db.add_event(
thread_id=request.thread_id,
@@ -158,7 +167,10 @@ async def get_events(
# ============================================================================
@app.post("/memories", response_model=MemoryResponse)
async def create_memory(request: CreateMemoryRequest):
async def create_memory(
request: CreateMemoryRequest,
service: Optional[dict] = Depends(get_current_service_optional)
):
"""Create long-term memory item"""
# Create in PostgreSQL
memory = await db.create_memory(