mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-09 13:35:12 +09:00
Adds cross-server Fleet Monitor endpoints and models, plus broad security and robustness improvements across agent, backend, and tooling. Key changes: - New fleet monitor API (endpoints for heatmap, comparison, alerts, thresholds, anomalies, forecast, search, exports, and Prometheus) and service skeleton. - New DB models for ServerAlertThreshold and MetricAlert to support metric alerts. - Agent discovery/registration hardening: HMAC-signed discovery requests, signature/timestamp validation, minimal discovery responses, HMAC-based unregister headers, and enforced command timeout cap in agent command handling. - WebSocket and agent client TLS handling gated by SERVERKIT_INSECURE_TLS env var; added auth rate limiting on agent gateway. - Docker-compose: parameterize agent image via AGENT_VERSION and avoid running as root by adding docker group access. - Backend security & policy: tightened CSP script/style sources, request size limit, 2FA pending-token rejection on non-2FA endpoints, registration flow/guards, admin-only SSO admin routes via middleware, rate limits on sensitive endpoints, and logging for auth/2FA events. - Database service hardening: identifier validation, avoid passing passwords on CLI (use env vars), safer subprocess usage, and docker exec handling improvements. - CronService: command validation to prevent shell injection and run commands without shell=True; build_service and other subprocess usages updated similarly. - Misc: .env.example notes clarified for encryption key generation, invitation token generation improved, agent registry timestamp window reduced, various logging and input validations added, frontend FleetMonitor page and styles stubbed in, and Vite/config/style updates. Overall this commit implements fleet monitoring scaffolding and a set of security, validation, and operational improvements to make agent discovery, registration, command execution, and backend services safer and more robust.
95 lines
2.8 KiB
Python
95 lines
2.8 KiB
Python
"""
|
|
Cryptographic utilities for ServerKit.
|
|
|
|
Provides encryption/decryption for sensitive data like API secrets.
|
|
"""
|
|
|
|
import os
|
|
import base64
|
|
import warnings
|
|
import logging
|
|
from cryptography.fernet import Fernet, InvalidToken
|
|
from cryptography.hazmat.primitives import hashes
|
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_encryption_key() -> bytes:
|
|
"""
|
|
Get the encryption key from environment variable.
|
|
|
|
The key should be a valid Fernet key (32 url-safe base64-encoded bytes).
|
|
Generate one with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
|
|
Returns:
|
|
bytes: The encryption key
|
|
|
|
Raises:
|
|
ValueError: If SERVERKIT_ENCRYPTION_KEY is not set
|
|
"""
|
|
key = os.environ.get('SERVERKIT_ENCRYPTION_KEY')
|
|
if not key:
|
|
if os.environ.get('FLASK_ENV') == 'production':
|
|
raise ValueError('CRITICAL: SERVERKIT_ENCRYPTION_KEY must be set in production')
|
|
logger.warning('SECURITY WARNING: Using derived development encryption key. Set SERVERKIT_ENCRYPTION_KEY for production.')
|
|
warnings.warn('Using derived development encryption key - not suitable for production')
|
|
# In development, use a default key (NOT for production!)
|
|
# This allows the system to work without explicit configuration
|
|
default_key = "DEV_ONLY_NOT_SECURE_CHANGE_IN_PRODUCTION_KEY"
|
|
# Derive a proper Fernet key from the default
|
|
kdf = PBKDF2HMAC(
|
|
algorithm=hashes.SHA256(),
|
|
length=32,
|
|
salt=b'serverkit_dev_salt',
|
|
iterations=100000,
|
|
)
|
|
key_bytes = base64.urlsafe_b64encode(kdf.derive(default_key.encode()))
|
|
return key_bytes
|
|
return key.encode()
|
|
|
|
|
|
def encrypt_secret(plaintext: str) -> str:
|
|
"""
|
|
Encrypt a secret using Fernet symmetric encryption.
|
|
|
|
Args:
|
|
plaintext: The secret to encrypt
|
|
|
|
Returns:
|
|
str: Base64-encoded encrypted data
|
|
"""
|
|
key = get_encryption_key()
|
|
f = Fernet(key)
|
|
encrypted = f.encrypt(plaintext.encode())
|
|
return encrypted.decode()
|
|
|
|
|
|
def decrypt_secret(encrypted: str) -> str:
|
|
"""
|
|
Decrypt a secret encrypted with encrypt_secret.
|
|
|
|
Args:
|
|
encrypted: Base64-encoded encrypted data
|
|
|
|
Returns:
|
|
str: The decrypted plaintext
|
|
|
|
Raises:
|
|
InvalidToken: If decryption fails (wrong key or corrupted data)
|
|
"""
|
|
key = get_encryption_key()
|
|
f = Fernet(key)
|
|
decrypted = f.decrypt(encrypted.encode())
|
|
return decrypted.decode()
|
|
|
|
|
|
def is_encryption_configured() -> bool:
|
|
"""
|
|
Check if encryption is properly configured.
|
|
|
|
Returns:
|
|
bool: True if SERVERKIT_ENCRYPTION_KEY is set
|
|
"""
|
|
return os.environ.get('SERVERKIT_ENCRYPTION_KEY') is not None
|