🔐 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:
31
infrastructure/kubernetes/apps/qdrant/auth-config.yaml
Normal file
31
infrastructure/kubernetes/apps/qdrant/auth-config.yaml
Normal 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
|
||||
39
infrastructure/kubernetes/nats/auth-config.yaml
Normal file
39
infrastructure/kubernetes/nats/auth-config.yaml
Normal 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
|
||||
}
|
||||
93
infrastructure/test-full-flow.sh
Executable file
93
infrastructure/test-full-flow.sh
Executable 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 "✅ Тестування завершено!"
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user