Initial commit
Dieser Commit ist enthalten in:
46
v2_lizenzserver/app/core/api_key_auth.py
Normale Datei
46
v2_lizenzserver/app/core/api_key_auth.py
Normale Datei
@ -0,0 +1,46 @@
|
||||
from fastapi import HTTPException, Request, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from app.db.database import get_db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def validate_api_key(request: Request, db: Session = Depends(get_db)):
|
||||
"""Validate API key from X-API-Key header against system_api_key table"""
|
||||
api_key = request.headers.get("X-API-Key")
|
||||
|
||||
if not api_key:
|
||||
logger.warning("API request without API key")
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="API key required",
|
||||
headers={"WWW-Authenticate": "ApiKey"}
|
||||
)
|
||||
|
||||
# Query the system API key
|
||||
cursor = db.execute(
|
||||
text("SELECT api_key FROM system_api_key WHERE id = 1")
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result or result[0] != api_key:
|
||||
logger.warning(f"Invalid API key attempt: {api_key[:8]}...")
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Invalid API key"
|
||||
)
|
||||
|
||||
# Update usage statistics
|
||||
db.execute(text("""
|
||||
UPDATE system_api_key
|
||||
SET last_used_at = CURRENT_TIMESTAMP,
|
||||
usage_count = usage_count + 1
|
||||
WHERE id = 1
|
||||
"""))
|
||||
db.commit()
|
||||
|
||||
return api_key
|
||||
32
v2_lizenzserver/app/core/config.py
Normale Datei
32
v2_lizenzserver/app/core/config.py
Normale Datei
@ -0,0 +1,32 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import List
|
||||
|
||||
class Settings(BaseSettings):
|
||||
PROJECT_NAME: str = "License Server"
|
||||
VERSION: str = "1.0.0"
|
||||
API_PREFIX: str = "/api"
|
||||
|
||||
SECRET_KEY: str = "your-secret-key-change-this-in-production"
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
|
||||
DATABASE_URL: str = "postgresql://license_user:license_password@db:5432/license_db"
|
||||
|
||||
|
||||
ALLOWED_ORIGINS: List[str] = [
|
||||
"https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com",
|
||||
"https://admin-panel-undso.z5m7q9dk3ah2v1plx6ju.com"
|
||||
]
|
||||
|
||||
DEBUG: bool = False
|
||||
|
||||
MAX_ACTIVATIONS_PER_LICENSE: int = 5
|
||||
HEARTBEAT_INTERVAL_MINUTES: int = 15
|
||||
OFFLINE_GRACE_PERIOD_DAYS: int = 7
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
extra = "ignore" # Ignore extra environment variables
|
||||
|
||||
settings = Settings()
|
||||
175
v2_lizenzserver/app/core/metrics.py
Normale Datei
175
v2_lizenzserver/app/core/metrics.py
Normale Datei
@ -0,0 +1,175 @@
|
||||
from prometheus_client import Counter, Histogram, Gauge, Info
|
||||
from functools import wraps
|
||||
import time
|
||||
|
||||
# License validation metrics
|
||||
license_validation_total = Counter(
|
||||
'license_validation_total',
|
||||
'Total number of license validations',
|
||||
['result', 'license_type']
|
||||
)
|
||||
|
||||
license_validation_errors_total = Counter(
|
||||
'license_validation_errors_total',
|
||||
'Total number of license validation errors',
|
||||
['error_type']
|
||||
)
|
||||
|
||||
license_validation_duration_seconds = Histogram(
|
||||
'license_validation_duration_seconds',
|
||||
'License validation duration in seconds',
|
||||
buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
|
||||
)
|
||||
|
||||
# Active licenses gauge
|
||||
active_licenses_total = Gauge(
|
||||
'active_licenses_total',
|
||||
'Total number of active licenses',
|
||||
['license_type']
|
||||
)
|
||||
|
||||
# Heartbeat metrics
|
||||
license_heartbeat_total = Counter(
|
||||
'license_heartbeat_total',
|
||||
'Total number of license heartbeats received'
|
||||
)
|
||||
|
||||
# Activation metrics
|
||||
license_activation_total = Counter(
|
||||
'license_activation_total',
|
||||
'Total number of license activations',
|
||||
['result']
|
||||
)
|
||||
|
||||
# Anomaly detection metrics
|
||||
anomaly_detections_total = Counter(
|
||||
'anomaly_detections_total',
|
||||
'Total number of anomalies detected',
|
||||
['anomaly_type', 'severity']
|
||||
)
|
||||
|
||||
# Concurrent sessions gauge
|
||||
concurrent_sessions_total = Gauge(
|
||||
'concurrent_sessions_total',
|
||||
'Total number of concurrent active sessions'
|
||||
)
|
||||
|
||||
# Database connection pool metrics
|
||||
db_connection_pool_size = Gauge(
|
||||
'db_connection_pool_size',
|
||||
'Database connection pool size'
|
||||
)
|
||||
|
||||
db_connection_pool_used = Gauge(
|
||||
'db_connection_pool_used',
|
||||
'Database connections currently in use'
|
||||
)
|
||||
|
||||
# API client metrics
|
||||
api_requests_total = Counter(
|
||||
'api_requests_total',
|
||||
'Total number of API requests',
|
||||
['method', 'endpoint', 'status']
|
||||
)
|
||||
|
||||
api_request_duration_seconds = Histogram(
|
||||
'api_request_duration_seconds',
|
||||
'API request duration in seconds',
|
||||
['method', 'endpoint'],
|
||||
buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
|
||||
)
|
||||
|
||||
# Cache metrics
|
||||
cache_hits_total = Counter(
|
||||
'cache_hits_total',
|
||||
'Total number of cache hits',
|
||||
['cache_type']
|
||||
)
|
||||
|
||||
cache_misses_total = Counter(
|
||||
'cache_misses_total',
|
||||
'Total number of cache misses',
|
||||
['cache_type']
|
||||
)
|
||||
|
||||
# System info
|
||||
system_info = Info(
|
||||
'license_server_info',
|
||||
'License server information'
|
||||
)
|
||||
|
||||
def track_request_metrics(method: str, endpoint: str):
|
||||
"""Decorator to track API request metrics"""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
status = "success"
|
||||
|
||||
try:
|
||||
result = await func(*args, **kwargs)
|
||||
return result
|
||||
except Exception as e:
|
||||
status = "error"
|
||||
raise
|
||||
finally:
|
||||
duration = time.time() - start_time
|
||||
api_requests_total.labels(
|
||||
method=method,
|
||||
endpoint=endpoint,
|
||||
status=status
|
||||
).inc()
|
||||
api_request_duration_seconds.labels(
|
||||
method=method,
|
||||
endpoint=endpoint
|
||||
).observe(duration)
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def track_validation_metrics():
|
||||
"""Decorator to track license validation metrics"""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
result = await func(*args, **kwargs)
|
||||
|
||||
# Extract result type from the validation result
|
||||
if result.get('valid'):
|
||||
result_type = 'success'
|
||||
elif result.get('error') == 'expired':
|
||||
result_type = 'expired'
|
||||
elif result.get('error') == 'invalid':
|
||||
result_type = 'invalid'
|
||||
else:
|
||||
result_type = 'error'
|
||||
|
||||
license_type = result.get('license_type', 'unknown')
|
||||
license_validation_total.labels(
|
||||
result=result_type,
|
||||
license_type=license_type
|
||||
).inc()
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
license_validation_errors_total.labels(
|
||||
error_type=type(e).__name__
|
||||
).inc()
|
||||
raise
|
||||
finally:
|
||||
duration = time.time() - start_time
|
||||
license_validation_duration_seconds.observe(duration)
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
# Initialize system info
|
||||
def init_metrics(version: str = "1.0.0"):
|
||||
"""Initialize system metrics"""
|
||||
system_info.info({
|
||||
'version': version,
|
||||
'service': 'license-server'
|
||||
})
|
||||
52
v2_lizenzserver/app/core/security.py
Normale Datei
52
v2_lizenzserver/app/core/security.py
Normale Datei
@ -0,0 +1,52 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import HTTPException, Security, Depends
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.database import get_db
|
||||
from app.models.models import ApiKey
|
||||
from app.core.config import settings
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
security = HTTPBearer()
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
|
||||
token = credentials.credentials
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
return payload
|
||||
except JWTError:
|
||||
raise HTTPException(status_code=403, detail="Invalid token")
|
||||
|
||||
def verify_api_key(api_key: str, db: Session):
|
||||
key = db.query(ApiKey).filter(
|
||||
ApiKey.key == api_key,
|
||||
ApiKey.is_active == True
|
||||
).first()
|
||||
|
||||
if not key:
|
||||
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||
|
||||
key.last_used = datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
return key
|
||||
|
||||
def get_api_key(
|
||||
credentials: HTTPAuthorizationCredentials = Security(security),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
api_key = credentials.credentials
|
||||
return verify_api_key(api_key, db)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren