fix: fail closed on production settings
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Literal
|
||||
|
||||
|
||||
LEGACY_DEFAULT_SECRET = "change-me-to-a-long-random-string"
|
||||
@@ -9,7 +10,7 @@ LEGACY_DEFAULT_SECRET = "change-me-to-a-long-random-string"
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# ---------- 运行环境 ----------
|
||||
environment: str = "development"
|
||||
environment: Literal["development", "test", "production"] = "development"
|
||||
|
||||
# ---------- 数据库 ----------
|
||||
DATABASE_URL: str = "sqlite+aiosqlite:///./pingwatch.db"
|
||||
|
||||
@@ -6,7 +6,7 @@ from app.config import LEGACY_DEFAULT_SECRET, Settings
|
||||
def validate_runtime_settings(settings: Settings) -> None:
|
||||
"""Reject unsafe settings before scheduling monitoring work."""
|
||||
if settings.environment == "production":
|
||||
if settings.secret_key == LEGACY_DEFAULT_SECRET:
|
||||
if not settings.secret_key.strip() or settings.secret_key == LEGACY_DEFAULT_SECRET:
|
||||
raise ValueError("SECRET_KEY must be provided by the runtime environment")
|
||||
if not settings.casdoor_endpoint.startswith("https://"):
|
||||
raise ValueError("CASDOOR endpoint must use TLS in production")
|
||||
|
||||
@@ -15,11 +15,41 @@ def test_production_rejects_legacy_jwt_secret():
|
||||
validate_runtime_settings(settings)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("secret_key", ["", " ", "\t\n"])
|
||||
def test_production_rejects_blank_jwt_secret(secret_key):
|
||||
settings = Settings(environment="production", secret_key=secret_key)
|
||||
|
||||
with pytest.raises(ValueError, match="SECRET_KEY"):
|
||||
validate_runtime_settings(settings)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("environment", ["prod", "Production", "staging"])
|
||||
def test_environment_must_be_an_explicit_supported_value(environment):
|
||||
with pytest.raises(ValidationError, match="environment"):
|
||||
Settings(environment=environment)
|
||||
|
||||
|
||||
def test_probe_packet_count_must_be_positive():
|
||||
with pytest.raises(ValidationError):
|
||||
Settings(probe_packets_per_round=0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "invalid_value"),
|
||||
[
|
||||
("offline_consecutive_rounds", 0),
|
||||
("degraded_window_rounds", 1),
|
||||
("degraded_loss_percent", 0.0),
|
||||
("recovery_consecutive_clean_rounds", 0),
|
||||
("wecom_notification_max_attempts", 0),
|
||||
("wecom_retry_base_seconds", 0),
|
||||
],
|
||||
)
|
||||
def test_monitoring_and_retry_settings_reject_values_below_their_bounds(field, invalid_value):
|
||||
with pytest.raises(ValidationError):
|
||||
Settings(**{field: invalid_value})
|
||||
|
||||
|
||||
def test_production_rejects_tls_disabled_casdoor():
|
||||
settings = Settings(
|
||||
environment="production",
|
||||
|
||||
Reference in New Issue
Block a user