Files
taylanbakircioglu d7ca50b387 feat: Flowfish - eBPF-powered multi-cluster Kubernetes observability platform
Multi-cluster dependency mapping, real-time network monitoring,
impact analysis, and CI/CD integration capabilities.

Made-with: Cursor
2026-03-29 21:43:20 +03:00

161 lines
4.9 KiB
Python

"""
Encryption utilities for sensitive data
Uses Fernet (AES-128-CBC) for symmetric encryption
SECURITY NOTES:
- ENCRYPTION_KEY must be set via environment variable
- Key should be generated with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
- Store key securely (Kubernetes Secret, HashiCorp Vault, etc.)
- Never commit encryption keys to version control
"""
import os
import base64
from typing import Optional
from cryptography.fernet import Fernet, InvalidToken
import structlog
logger = structlog.get_logger()
# Get encryption key from environment
# MUST be a valid Fernet key (32 url-safe base64-encoded bytes)
_ENCRYPTION_KEY = os.environ.get("FLOWFISH_ENCRYPTION_KEY")
# Lazy initialization of Fernet cipher
_cipher: Optional[Fernet] = None
def _get_cipher() -> Fernet:
"""Get or initialize Fernet cipher"""
global _cipher
if _cipher is None:
if not _ENCRYPTION_KEY:
# In development, generate a warning and use a default key
# In production, this should NEVER happen
logger.warning(
"FLOWFISH_ENCRYPTION_KEY not set! Using temporary key. "
"THIS IS INSECURE FOR PRODUCTION!"
)
# Generate a temporary key for development only
_cipher = Fernet(Fernet.generate_key())
else:
try:
_cipher = Fernet(_ENCRYPTION_KEY.encode())
except Exception as e:
logger.error("Invalid FLOWFISH_ENCRYPTION_KEY", error=str(e))
raise ValueError(
"Invalid FLOWFISH_ENCRYPTION_KEY. "
"Generate with: python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\""
)
return _cipher
def encrypt_value(plaintext: str) -> str:
"""
Encrypt a string value using Fernet (AES-128-CBC)
Args:
plaintext: The string to encrypt
Returns:
Base64-encoded encrypted string
"""
if not plaintext:
return ""
cipher = _get_cipher()
encrypted = cipher.encrypt(plaintext.encode('utf-8'))
return encrypted.decode('utf-8')
def decrypt_value(ciphertext: str) -> str:
"""
Decrypt a Fernet-encrypted string with backward compatibility.
If decryption fails (e.g., data is plain text or wrong key),
returns the original value as-is. This ensures:
- Backward compatibility with existing unencrypted data
- Graceful handling of encryption key changes
- No application crashes on decrypt failure
Args:
ciphertext: Base64-encoded encrypted string (or plain text for legacy data)
Returns:
Decrypted plaintext string, or original value if decryption fails
"""
if not ciphertext:
return ""
# Check if value looks like it could be Fernet encrypted
# Fernet tokens start with 'gAAAAA' (base64 encoded version byte)
if not ciphertext.startswith('gAAAAA'):
# Likely plain text (legacy unencrypted data)
logger.debug("Value doesn't appear to be encrypted, returning as-is")
return ciphertext
cipher = _get_cipher()
try:
decrypted = cipher.decrypt(ciphertext.encode('utf-8'))
return decrypted.decode('utf-8')
except InvalidToken:
# Decryption failed - could be plain text or wrong key
# Return original value as fallback for backward compatibility
logger.warning(
"Decryption failed, returning value as-is. "
"This may indicate legacy unencrypted data or key mismatch."
)
return ciphertext
except Exception as e:
# Unexpected error - log and return original
logger.error("Unexpected decryption error", error=str(e))
return ciphertext
def encrypt_token(token: str) -> str:
"""Encrypt a service account token"""
return encrypt_value(token)
def decrypt_token(encrypted_token: str) -> str:
"""Decrypt a service account token"""
return decrypt_value(encrypted_token)
def encrypt_certificate(cert: str) -> str:
"""Encrypt a CA certificate"""
return encrypt_value(cert)
def decrypt_certificate(encrypted_cert: str) -> str:
"""Decrypt a CA certificate"""
return decrypt_value(encrypted_cert)
def encrypt_kubeconfig(kubeconfig: str) -> str:
"""Encrypt a kubeconfig file content"""
return encrypt_value(kubeconfig)
def decrypt_kubeconfig(encrypted_kubeconfig: str) -> str:
"""Decrypt a kubeconfig file content"""
return decrypt_value(encrypted_kubeconfig)
def is_encryption_configured() -> bool:
"""Check if encryption is properly configured"""
return bool(_ENCRYPTION_KEY)
def generate_encryption_key() -> str:
"""Generate a new Fernet encryption key"""
return Fernet.generate_key().decode()
# Aliases for backward compatibility
encrypt_data = encrypt_value
decrypt_data = decrypt_value