🔐 Auth: базова реалізація JWT для Memory Service

- JWT middleware для FastAPI
- Генерація/перевірка JWT токенів
- Скрипти для генерації Qdrant API keys
- Скрипти для генерації NATS operator JWT
- План реалізації Auth

TODO: Додати JWT до endpoints, NATS nkeys config, Qdrant API key config
This commit is contained in:
Apple
2026-01-10 10:43:14 -08:00
parent 0ebbb172f0
commit 6c426bc274
6 changed files with 313 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# 🔐 План реалізації Auth
**Дата:** 2026-01-10
**Версія:** 1.0.0
---
## 📋 Компоненти для Auth
### 1. NATS (nkeys)
**Статус:** TODO
**Пріоритет:** Високий
**Що робити:**
- Генерація NATS operator JWT
- Створення system account
- Створення user accounts для сервісів
- Оновлення NATS конфігурації з auth
**Файли:**
- `infrastructure/auth/nats/generate-operator.sh` — генерація operator
- `infrastructure/auth/nats/create-accounts.sh` — створення accounts
- `infrastructure/kubernetes/nats/auth-secrets.yaml` — Secrets для JWT
---
### 2. Memory Service (JWT)
**Статус:** TODO
**Пріоритет:** Високий
**Що робити:**
- Додати JWT middleware до FastAPI
- Генерація JWT токенів для сервісів
- Перевірка JWT в Memory Service endpoints
- Створення service-to-service токенів
**Файли:**
- `services/memory-service/app/auth.py` — JWT middleware
- `services/memory-service/app/jwt_utils.py` — генерація/перевірка JWT
- `infrastructure/kubernetes/apps/memory-service/auth-secrets.yaml` — Secrets
---
### 3. Qdrant (API key)
**Статус:** TODO
**Пріоритет:** Середній
**Що робити:**
- Увімкнути API key auth в Qdrant
- Генерація API ключів для кожного сервісу
- Оновлення Memory Service для використання API key
- Оновлення worker-daemon для використання API key
**Файли:**
- `infrastructure/kubernetes/apps/qdrant/config.yaml` — Qdrant config з auth
- `infrastructure/auth/qdrant/generate-keys.sh` — генерація ключів
- `infrastructure/kubernetes/apps/qdrant/auth-secrets.yaml` — Secrets
---
## 🔄 Порядок реалізації
1. **Memory Service JWT** (найпростіше, найважливіше)
2. **NATS nkeys** (середня складність, важливо для безпеки)
3. **Qdrant API key** (найпростіше, але менш критично)
---
*Документ створено: 2026-01-10 19:30 CET*

View File

@@ -0,0 +1,38 @@
#!/bin/bash
# Генерація NATS operator JWT та accounts
set -e
echo "🔑 Генерація NATS operator JWT..."
# Перевірка наявності nsc (NATS CLI для управління accounts)
if ! command -v nsc &> /dev/null; then
echo "⚠️ nsc не встановлено. Встановіть: https://github.com/nats-io/natscli"
echo " Або використайте Docker: docker run -it --rm natsio/nats-box"
exit 1
fi
# Створення operator
OPERATOR_NAME="DAARION"
echo "Створення operator: $OPERATOR_NAME"
nsc add operator "$OPERATOR_NAME"
# Створення system account
SYSTEM_ACCOUNT="SYSTEM"
echo "Створення system account: $SYSTEM_ACCOUNT"
nsc add account "$SYSTEM_ACCOUNT"
# Створення user accounts для сервісів
echo "Створення user accounts..."
nsc add user --account "$SYSTEM_ACCOUNT" memory-service
nsc add user --account "$SYSTEM_ACCOUNT" worker-daemon
nsc add user --account "$SYSTEM_ACCOUNT" matrix-gateway
echo ""
echo "✅ Operator та accounts створено!"
echo ""
echo "JWT файли знаходяться в: ~/.nsc/"
echo " Operator JWT: ~/.nsc/nats/$OPERATOR_NAME/$OPERATOR_NAME.jwt"
echo " System Account JWT: ~/.nsc/nats/$OPERATOR_NAME/accounts/$SYSTEM_ACCOUNT/$SYSTEM_ACCOUNT.jwt"
echo ""
echo "⚠️ Збережіть ці JWT в Vault або K8s Secrets!"

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Генерація API ключів для Qdrant
set -e
echo "🔑 Генерація API ключів для Qdrant..."
# Генерація випадкових ключів
MEMORY_SERVICE_KEY=$(openssl rand -hex 32)
WORKER_DAEMON_KEY=$(openssl rand -hex 32)
MATRIX_GATEWAY_KEY=$(openssl rand -hex 32)
echo ""
echo "✅ Згенеровано API ключі:"
echo " Memory Service: $MEMORY_SERVICE_KEY"
echo " Worker Daemon: $WORKER_DAEMON_KEY"
echo " Matrix Gateway: $MATRIX_GATEWAY_KEY"
echo ""
echo "⚠️ Збережіть ці ключі в Vault або K8s Secrets!"
echo ""
echo "Для Qdrant config додайте:"
echo " QDRANT__SERVICE__API_KEY: $MEMORY_SERVICE_KEY"

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""
Тестовий скрипт для Matrix Gateway
Перевірка базової функціональності без реального Matrix connection
"""
import asyncio
import json
from gateway.job_creator import JobCreator
from gateway.nats_publisher import NATSPublisher
async def test_job_creation():
"""Тест створення job"""
print("🧪 Тест створення job...")
# Mock NATS publisher
class MockNATSPublisher:
async def connect(self):
pass
async def disconnect(self):
pass
publisher = MockNATSPublisher()
creator = JobCreator(publisher)
# Тест команди embed
command = {
"type": "embed",
"priority": "online",
"input": {
"text": ["Привіт, це тестовий текст"],
"model": "cohere/embed-multilingual-v3.0",
"dims": 1024
}
}
job = await creator.create_job(command)
print(f"✅ Job створено:")
print(f" Job ID: {job['job_id']}")
print(f" Type: {job['type']}")
print(f" Priority: {job['priority']}")
print(f" Idempotency Key: {job['idempotency_key']}")
print(f" Requirements: {job['requirements']}")
# Перевірка структури
assert "job_id" in job
assert "idempotency_key" in job
assert job["idempotency_key"].startswith("sha256:")
assert job["type"] == "embed"
assert job["priority"] == "online"
print("Всі перевірки пройдено!")
return True
async def test_command_parsing():
"""Тест парсингу команд"""
print("\n🧪 Тест парсингу команд...")
from gateway.main import MatrixGateway
gateway = MatrixGateway()
# Тест !embed
command = gateway._parse_command("!embed Привіт, це тест")
assert command is not None
assert command["type"] == "embed"
assert command["priority"] == "online"
assert command["input"]["text"] == ["Привіт, це тест"]
print("✅ !embed команда парситься правильно")
# Тест !retrieve
command = gateway._parse_command("!retrieve пошуковий запит")
assert command is not None
assert command["type"] == "retrieve"
assert command["priority"] == "online"
print("✅ !retrieve команда парситься правильно")
# Тест !summarize
command = gateway._parse_command("!summarize thread-123")
assert command is not None
assert command["type"] == "summarize"
assert command["priority"] == "offline"
print("✅ !summarize команда парситься правильно")
# Тест невірної команди
command = gateway._parse_command("!unknown команда")
assert command is None
print("✅ Невірна команда правильно відхиляється")
print("Всі перевірки парсингу пройдено!")
return True
async def main():
"""Головна функція тестування"""
print("🚀 Тестування Matrix Gateway\n")
try:
await test_job_creation()
await test_command_parsing()
print("\nВсі тести пройдено успішно!")
return 0
except AssertionError as e:
print(f"\n❌ Тест не пройдено: {e}")
return 1
except Exception as e:
print(f"\n❌ Помилка: {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
exit(asyncio.run(main()))

View File

@@ -0,0 +1,58 @@
"""
JWT Authentication для Memory Service
"""
import os
import jwt
import time
from typing import Optional
from fastapi import HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from app.config import get_settings
settings = get_settings()
# JWT settings
JWT_SECRET = settings.jwt_secret or os.getenv("MEMORY_JWT_SECRET", "change-me-in-production")
JWT_ALGORITHM = settings.jwt_algorithm
JWT_EXPIRATION = settings.jwt_expiration
security = HTTPBearer()
def generate_jwt_token(service_name: str, permissions: list = None) -> str:
"""Генерація JWT токену для сервісу"""
payload = {
"service": service_name,
"permissions": permissions or ["read", "write"],
"iat": int(time.time()),
"exp": int(time.time()) + JWT_EXPIRATION
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def verify_jwt_token(token: str) -> dict:
"""Перевірка JWT токену"""
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
async def get_current_service(credentials: HTTPAuthorizationCredentials = Security(security)) -> dict:
"""Dependency для отримання поточного сервісу з JWT"""
token = credentials.credentials
payload = verify_jwt_token(token)
return payload
def require_permission(permission: str):
"""Decorator для перевірки прав доступу"""
async def permission_checker(service: dict = Security(get_current_service)):
if permission not in service.get("permissions", []):
raise HTTPException(status_code=403, detail=f"Permission '{permission}' required")
return service
return permission_checker

View File

@@ -46,6 +46,11 @@ class Settings(BaseSettings):
memory_confirm_boost: float = 0.1
memory_reject_penalty: float = 0.3
# JWT Auth
jwt_secret: str = "" # Must be set via MEMORY_JWT_SECRET env var or Vault
jwt_algorithm: str = "HS256"
jwt_expiration: int = 3600 # 1 година
class Config:
env_prefix = "MEMORY_"
env_file = ".env"