Fix production deployment issues

This commit is contained in:
2026-03-28 13:57:57 +03:00
Unverified
parent 30ab4d9190
commit 3caadde6ff
14 changed files with 170 additions and 105 deletions
+6 -5
View File
@@ -92,13 +92,14 @@ from fastapi import UploadFile, File, HTTPException, Request, Depends
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
# File serving directories (matching main service structure)
FILES_BASE_DIR = Path("data/uploads/files")
FILES_NORMAL_DIR = FILES_BASE_DIR / "normal"
FILES_ENCRYPTED_DIR = FILES_BASE_DIR / "encrypted"
# Base storage directories
BASE_DIR = Path("files")
# Legacy upload layout (was data/uploads/files on monolith main under /app/data).
# Keep under BASE_DIR so Docker uses the file_storage volume (/app/files), not /app/data
# (different uid / optional mount → PermissionError on prod).
FILES_BASE_DIR = BASE_DIR / "data" / "uploads" / "files"
FILES_NORMAL_DIR = FILES_BASE_DIR / "normal"
FILES_ENCRYPTED_DIR = FILES_BASE_DIR / "encrypted"
FILES_DIR = BASE_DIR / "files"
THUMBS_DIR = BASE_DIR / "thumbs"
TMP_DIR = BASE_DIR / "tmp"
-9
View File
@@ -319,15 +319,6 @@ async def health_check():
return {"status": "healthy", "service": "main"}
@app.get("/key/public")
async def key_public_proxy():
"""
Proxy endpoint for messaging public key. In dev this calls the in-process function,
in production it will proxy to the external messaging service via the keys helper.
"""
return await keys.get_public_key()
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("PORT", "8300"))
+2 -38
View File
@@ -1,10 +1,8 @@
from fastapi import APIRouter, Depends, HTTPException
from typing import Dict, Any
from fastapi import APIRouter, HTTPException
import os
import logging
import base64
router = APIRouter(prefix="/api")
router = APIRouter()
logger = logging.getLogger("uvicorn.error")
@@ -58,37 +56,3 @@ async def get_public_key():
logger.error(f"Failed to fetch messaging public key via HTTP: {e}")
raise HTTPException(status_code=502, detail="Failed to contact messaging service")
@router.post("/key/invalidate")
async def invalidate_key():
"""
Request messaging service to invalidate its current ephemeral key (rotate).
"""
messaging_module = _get_messaging_module()
if messaging_module:
try:
data = await messaging_module.invalidate_key() # type: ignore
return data
except Exception as e:
logger.error(f"Failed to invalidate key in in-process messaging module: {e}")
raise HTTPException(status_code=500, detail="Failed to invalidate messaging key")
messaging_url = os.getenv("MESSAGING_SERVICE_URL", "http://messaging:8301")
url = f"{messaging_url.rstrip('/')}/key/invalidate"
try:
try:
import httpx
resp = httpx.post(url, timeout=5.0)
resp.raise_for_status()
return resp.json()
except Exception:
from urllib import request, error
import json
req = request.Request(url, method="POST")
with request.urlopen(req, timeout=5) as r:
body = r.read()
return json.loads(body)
except Exception as e:
logger.error(f"Failed to call messaging invalidate endpoint via HTTP: {e}")
raise HTTPException(status_code=502, detail="Failed to contact messaging service")
+2 -1
View File
@@ -137,7 +137,8 @@ def decrypt_transport_blob(
ephemeral transport private key and the client's public key.
Args:
client_public_key_b64: Sender public key in base64 (raw X25519).
client_public_key_b64: Client ephemeral public key in base64 (raw X25519),
the same key as used for the transport-encrypted message body.
encrypted_blob: Raw bytes of `nonce || ciphertext`.
ephemeral_private_key: Server ephemeral X25519 private key.
nonce_size: Nonce size in bytes (24 for XSalsa20-Poly1305).
+11 -6
View File
@@ -6,8 +6,7 @@ providing compliance access while ensuring zero-knowledge storage of plaintext c
API Endpoints:
- GET /health: Health check
- GET /key/public: Get current ephemeral transport public key
- POST /key/invalidate: Rotate ephemeral keys
- GET /key/transport/public: Get current ephemeral transport public key
- POST /process: Process encrypted message through envelope encryption pipeline
"""
@@ -203,7 +202,8 @@ class ProcessMessageRequest(BaseModel):
class ProcessMessageWithFilesFile(BaseModel):
"""
A single transport-encrypted file blob (base64 of nonce||ciphertext).
A single transport-encrypted file blob (base64 of nonce||ciphertext),
encrypted with the same ephemeral client key as the message body.
"""
encrypted_file_data_b64: str
filename: str = "file"
@@ -213,6 +213,8 @@ class ProcessMessageWithFilesRequest(ProcessMessageRequest):
"""
Process a transport-encrypted message and a list of transport-encrypted files
using a single MEK for the whole envelope.
Each file blob uses the same client_public_key_b64 / X25519 ephemeral pair as the message.
"""
files: list[ProcessMessageWithFilesFile]
@@ -353,6 +355,9 @@ async def process_message_with_files(
):
"""
In-process helper: process message + transport-encrypted files with one MEK.
File blobs must be encrypted with the same ephemeral client key as the message
(same client_public_key_b64), not the sender's long-term identity key.
transport_files: list of {"encrypted_file_data_b64": str, "filename": str}
"""
private_key = _get_ephemeral_private_key()
@@ -371,7 +376,7 @@ async def process_message_with_files(
transport_blob = base64.b64decode(enc_b64)
plaintext_files.append(
decrypt_transport_blob(
client_public_key_b64=sender_public_key_b64,
client_public_key_b64=client_public_key_b64,
encrypted_blob=transport_blob,
ephemeral_private_key=private_key,
)
@@ -393,8 +398,8 @@ async def process_message_with_files_http(request: ProcessMessageWithFilesReques
"""
Process an encrypted message and its files using a single MEK.
- Message transport layer is decrypted using the message client ephemeral key
- File transport layer is decrypted using the sender long-term public key
- Message and file transport layers use the same client ephemeral X25519 keypair
(client_public_key_b64); files are NaCl box ciphertexts to the server transport key
- One MEK is generated and used to encrypt message + all files
- MEK is wrapped for compliance, sender, and recipient (stored on DM envelope)
"""