chore: protect external runtime connection settings
This commit is contained in:
@@ -205,14 +205,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ DLL verified"
|
||||
# Production connection settings must come from the service
|
||||
# EnvironmentFile, never from a versioned appsettings file.
|
||||
SERVICE_ENV=$(sudo systemctl show "$SERVICE_NAME" -p EnvironmentFiles --value 2>/dev/null || true)
|
||||
if [ -z "$SERVICE_ENV" ]; then
|
||||
echo "ERROR: $SERVICE_NAME has no EnvironmentFile; database credentials are not configured"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ External service environment configured"
|
||||
echo "✓ Runtime configuration is managed outside the release artifact"
|
||||
|
||||
# 3. Update Symlink
|
||||
echo ""
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# 은퇴자산포트폴리오 투자 에이전트 운영 지침
|
||||
|
||||
## QuantEngine 운영 설정 권위
|
||||
- `ConnectionStrings__DefaultConnection`은 운영 설정에서 관리한다.
|
||||
- 저장소 코드, DbUp migration, CI artifact는 운영 계정 비밀번호를 생성하거나 덮어쓰지 않는다.
|
||||
- 배포/검증 하네스는 설정값을 읽기만 하며, 값 자체를 로그·증빙·커밋에 기록하지 않는다.
|
||||
- 설정 변경은 애플리케이션 배포와 분리된 운영 설정 변경으로 취급한다.
|
||||
|
||||
## 0. 최우선 원칙
|
||||
- 이 파일은 운영 인덱스다. 상세 규칙은 `governance/rules/*.yaml`와 `spec/*.yaml`를 우선한다.
|
||||
- 가격, 수량, TP/SL, 점수는 오직 `spec/13_formula_registry.yaml`와 하네스 산출값만 사용한다.
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"validate:normalized-learning-store": "python tools/validate_normalized_learning_store_v1.py",
|
||||
"validate:dotnet-cutover": "python tools/validate_dotnet_postgresql_json_cutover_v1.py",
|
||||
"validate:schema-model": "python tools/generate_schema_model_generation_evidence_v1.py && python tools/validate_schema_model_generation_v1.py",
|
||||
"validate:runtime-settings": "python tools/validate_runtime_connection_settings_immutability_v1.py",
|
||||
"test:e2e": "playwright test --project=chromium",
|
||||
"test:evidence": "playwright test --project=evidence"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
formula_id: RUNTIME_CONNECTION_SETTINGS_IMMUTABILITY_V1
|
||||
version: 1
|
||||
authority: AGENTS.md
|
||||
setting_key: ConnectionStrings__DefaultConnection
|
||||
owner: operations
|
||||
policy:
|
||||
source: runtime_environment_or_external_settings
|
||||
application_may_read: true
|
||||
application_may_write: false
|
||||
secret_value_in_repository: forbidden
|
||||
evidence: Temp/runtime_connection_settings_immutability_v1.json
|
||||
verification: python tools/validate_runtime_connection_settings_immutability_v1.py
|
||||
@@ -81,12 +81,6 @@ try
|
||||
// PostgreSQL Dapper Setup
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
||||
?? throw new InvalidOperationException("Connection string 'DefaultConnection' is required.");
|
||||
if (builder.Environment.IsProduction()
|
||||
&& string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection")))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Production requires ConnectionStrings__DefaultConnection from the service environment.");
|
||||
}
|
||||
|
||||
var configuredDatabase = new NpgsqlConnectionStringBuilder(connectionString).Database;
|
||||
if (!string.Equals(configuredDatabase, "quantenginedb", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FILES = [
|
||||
ROOT / "src/dotnet/QuantEngine.Web/Program.cs",
|
||||
ROOT / ".gitea/workflows/deploy-prod.yml",
|
||||
ROOT / "src/dotnet/QuantEngine.Web/appsettings.json",
|
||||
ROOT / "src/dotnet/QuantEngine.Web/appsettings.Development.json",
|
||||
]
|
||||
REPORT = ROOT / "Temp/runtime_connection_settings_immutability_v1.json"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
violations: list[str] = []
|
||||
for path in FILES:
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
if "ConnectionStrings__DefaultConnection=" in text:
|
||||
violations.append(f"embedded_connection_string:{path.relative_to(ROOT)}")
|
||||
if "Environment.SetEnvironmentVariable(\"ConnectionStrings__DefaultConnection\"" in text:
|
||||
violations.append(f"runtime_write:{path.relative_to(ROOT)}")
|
||||
payload = {
|
||||
"formula_id": "RUNTIME_CONNECTION_SETTINGS_IMMUTABILITY_V1",
|
||||
"gate": "PASS" if not violations else "FAIL",
|
||||
"setting_key": "ConnectionStrings__DefaultConnection",
|
||||
"source": "external_runtime_settings",
|
||||
"violations": violations,
|
||||
}
|
||||
REPORT.parent.mkdir(parents=True, exist_ok=True)
|
||||
REPORT.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
return 0 if payload["gate"] == "PASS" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user