🔐 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:
72
infrastructure/auth/AUTH-IMPLEMENTATION-PLAN.md
Normal file
72
infrastructure/auth/AUTH-IMPLEMENTATION-PLAN.md
Normal 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*
|
||||||
38
infrastructure/auth/nats/generate-operator.sh
Executable file
38
infrastructure/auth/nats/generate-operator.sh
Executable 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!"
|
||||||
22
infrastructure/auth/qdrant/generate-keys.sh
Executable file
22
infrastructure/auth/qdrant/generate-keys.sh
Executable 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"
|
||||||
118
infrastructure/matrix-gateway/test_gateway.py
Executable file
118
infrastructure/matrix-gateway/test_gateway.py
Executable 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()))
|
||||||
58
services/memory-service/app/auth.py
Normal file
58
services/memory-service/app/auth.py
Normal 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
|
||||||
@@ -46,6 +46,11 @@ class Settings(BaseSettings):
|
|||||||
memory_confirm_boost: float = 0.1
|
memory_confirm_boost: float = 0.1
|
||||||
memory_reject_penalty: float = 0.3
|
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:
|
class Config:
|
||||||
env_prefix = "MEMORY_"
|
env_prefix = "MEMORY_"
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
|
|||||||
Reference in New Issue
Block a user