🔐 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_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"
|
||||
|
||||
Reference in New Issue
Block a user