OpenHands/openhands/app_server/utils/encryption_key.py

66 lines
2.1 KiB
Python

import hashlib
import os
from datetime import datetime
from pathlib import Path
from typing import Any
import base62
from pydantic import BaseModel, Field, SecretStr, TypeAdapter, field_serializer
from openhands.agent_server.utils import utc_now
class EncryptionKey(BaseModel):
"""Configuration for an encryption key."""
id: str = Field(default_factory=lambda: base62.encodebytes(os.urandom(32)))
key: SecretStr
active: bool = True
notes: str | None = None
created_at: datetime = Field(default_factory=utc_now)
@field_serializer('key')
def serialize_key(self, key: SecretStr, info: Any):
"""Conditionally serialize the key based on context."""
if info.context and info.context.get('expose_secrets'):
return key.get_secret_value()
return str(key) # Returns '**********' by default
def get_default_encryption_keys(workspace_dir: Path) -> list[EncryptionKey]:
"""Generate default encryption keys."""
master_key = os.getenv('JWT_SECRET')
if master_key:
# Derive a deterministic key ID from the secret itself.
# This ensures all pods using the same JWT_SECRET get the same key ID,
# which is critical for multi-pod deployments where tokens may be
# created by one pod and verified by another.
key_id = base62.encodebytes(hashlib.sha256(master_key.encode()).digest())
return [
EncryptionKey(
id=key_id,
key=SecretStr(master_key),
active=True,
notes='jwt secret master key',
)
]
key_file = workspace_dir / '.keys'
type_adapter = TypeAdapter(list[EncryptionKey])
if key_file.exists():
encryption_keys = type_adapter.validate_json(key_file.read_text())
return encryption_keys
encryption_keys = [
EncryptionKey(
key=SecretStr(base62.encodebytes(os.urandom(32))),
active=True,
notes='generated master key',
)
]
json_data = type_adapter.dump_json(
encryption_keys, context={'expose_secrets': True}
)
key_file.write_bytes(json_data)
return encryption_keys