修复开发容器启动并加固部署配置
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.worktrees
|
||||||
|
.agents
|
||||||
|
.claude
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
**/.env
|
||||||
|
**/.env.*
|
||||||
|
pg_data
|
||||||
|
tmp
|
||||||
|
.install-logs
|
||||||
|
.pytest_cache
|
||||||
|
**/.pytest_cache
|
||||||
|
**/__pycache__
|
||||||
|
**/*.pyc
|
||||||
|
frontend/node_modules
|
||||||
|
frontend/dist
|
||||||
|
backend/.coverage
|
||||||
|
backend/.pytest_cache
|
||||||
|
nginx/certs
|
||||||
@@ -399,11 +399,11 @@ def _is_non_china_ip(ip_location: IpLocation) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def _classify_security_access_log(log: SecurityAccessLog, ip_location: IpLocation | None = None) -> dict[str, str]:
|
def _classify_security_access_log(log: SecurityAccessLog, ip_location: IpLocation | None = None) -> dict[str, str]:
|
||||||
if ip_location and _is_non_china_ip(ip_location):
|
|
||||||
return {"category": "ABNORMAL_IP", "severity": "HIGH"}
|
|
||||||
path = (log.path or "").lower()
|
path = (log.path or "").lower()
|
||||||
if any(marker in path for marker in SENSITIVE_PROBE_MARKERS):
|
if any(marker in path for marker in SENSITIVE_PROBE_MARKERS):
|
||||||
return {"category": "PROBE", "severity": "CRITICAL"}
|
return {"category": "PROBE", "severity": "CRITICAL"}
|
||||||
|
if ip_location and _is_non_china_ip(ip_location):
|
||||||
|
return {"category": "ABNORMAL_IP", "severity": "HIGH"}
|
||||||
if log.status_code >= 500:
|
if log.status_code >= 500:
|
||||||
return {"category": "SERVER_ERROR", "severity": "HIGH"}
|
return {"category": "SERVER_ERROR", "severity": "HIGH"}
|
||||||
if log.auth_status == "INVALID_TOKEN":
|
if log.auth_status == "INVALID_TOKEN":
|
||||||
|
|||||||
@@ -929,6 +929,41 @@ async def test_security_access_logs_classify_sensitive_path_probe(db_session):
|
|||||||
assert result["items"][0]["severity"] == "CRITICAL"
|
assert result["items"][0]["severity"] == "CRITICAL"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_security_access_logs_prioritize_sensitive_probe_over_foreign_ip(db_session, monkeypatch):
|
||||||
|
"""敏感路径探测应优先于海外 IP 分类,避免降低风险等级。"""
|
||||||
|
await db_session.execute(text("DELETE FROM security_access_logs"))
|
||||||
|
monkeypatch.setattr(
|
||||||
|
permission_monitoring,
|
||||||
|
"resolve_ip_location",
|
||||||
|
lambda ip: FakeIpInfo("South Holland", "", "", "Netherlands"),
|
||||||
|
)
|
||||||
|
await db_session.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
INSERT INTO security_access_logs
|
||||||
|
(id, method, path, status_code, elapsed_ms, client_ip, user_agent, auth_status, user_identifier, created_at)
|
||||||
|
VALUES
|
||||||
|
('00000000-0000-4000-8000-000000000505', 'GET', '/api/.git/config', 404, 0.7,
|
||||||
|
'45.148.10.95', 'probe-bot/1.0', 'ANONYMOUS', NULL, CURRENT_TIMESTAMP)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
result = await permission_monitoring.get_security_access_logs(
|
||||||
|
db=db_session,
|
||||||
|
_=AdminUserStub(),
|
||||||
|
status_min=400,
|
||||||
|
auth_status=None,
|
||||||
|
page=1,
|
||||||
|
page_size=20,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["items"][0]["category"] == "PROBE"
|
||||||
|
assert result["items"][0]["severity"] == "CRITICAL"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_security_access_logs_classify_non_china_ip_as_abnormal(db_session, monkeypatch):
|
async def test_security_access_logs_classify_non_china_ip_as_abnormal(db_session, monkeypatch):
|
||||||
"""安全事件明细应把非中国公网 IP 归类为异常 IP。"""
|
"""安全事件明细应把非中国公网 IP 归类为异常 IP。"""
|
||||||
|
|||||||
+12
-1
@@ -19,7 +19,18 @@ services:
|
|||||||
container_name: ctms_frontend_dev
|
container_name: ctms_frontend_dev
|
||||||
restart: always
|
restart: always
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
command: sh -c "npm ci && npm run dev -- --host 0.0.0.0"
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
lock_hash="$$(sha256sum package-lock.json | awk '{print $$1}')"
|
||||||
|
marker="node_modules/.ctms-package-lock.sha256"
|
||||||
|
if [ ! -f "$$marker" ] || [ "$$(cat "$$marker")" != "$$lock_hash" ]; then
|
||||||
|
npm ci
|
||||||
|
printf '%s' "$$lock_hash" > "$$marker"
|
||||||
|
fi
|
||||||
|
npm run dev -- --host 0.0.0.0
|
||||||
environment:
|
environment:
|
||||||
VITE_RUNTIME_ENV: ${ENV:-development}
|
VITE_RUNTIME_ENV: ${ENV:-development}
|
||||||
VITE_ALLOW_INSECURE_DEV_LOGIN: ${VITE_ALLOW_INSECURE_DEV_LOGIN:-true}
|
VITE_ALLOW_INSECURE_DEV_LOGIN: ${VITE_ALLOW_INSECURE_DEV_LOGIN:-true}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
# 持久化数据到 Linux 本地,防止重启丢失
|
# 持久化数据到 Linux 本地,防止重启丢失
|
||||||
- ./pg_data:/var/lib/postgresql/data
|
- ./pg_data:/var/lib/postgresql/data
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U ctms_user -d ctms_db"]
|
test: ["CMD-SHELL", "pg_isready -U ctms_user -d ctms_db"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
@@ -28,8 +26,6 @@ services:
|
|||||||
pull: false
|
pull: false
|
||||||
container_name: ctms_backend
|
container_name: ctms_backend
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
|
||||||
- "8000:8000"
|
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+asyncpg://ctms_user:secret_password@db/ctms_db
|
DATABASE_URL: postgresql+asyncpg://ctms_user:secret_password@db/ctms_db
|
||||||
ENV: ${ENV:-development}
|
ENV: ${ENV:-development}
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ http {
|
|||||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~* (^|/)(\.git|\.env($|[./_-])|wp-config\.php|config\.php|database\.yml|backup($|[._/-])) {
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://backend;
|
proxy_pass http://backend;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ http {
|
|||||||
add_header Cache-Control "public, max-age=86400" always;
|
add_header Cache-Control "public, max-age=86400" always;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~* (^|/)(\.git|\.env($|[./_-])|wp-config\.php|config\.php|database\.yml|backup($|[._/-])) {
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://backend;
|
proxy_pass http://backend;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@@ -552,9 +552,10 @@ PY
|
|||||||
|
|
||||||
check_http_endpoint() {
|
check_http_endpoint() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
|
local max_attempts="${2:-10}"
|
||||||
local url="${BASE_URL%/}$path"
|
local url="${BASE_URL%/}$path"
|
||||||
log "探测接口: $url"
|
log "探测接口: $url"
|
||||||
local attempt=1 max_attempts=10 delay=2 warmup_delay=3
|
local attempt=1 delay=2 warmup_delay=3
|
||||||
log "等待服务预热 ${warmup_delay}s …"
|
log "等待服务预热 ${warmup_delay}s …"
|
||||||
sleep "$warmup_delay"
|
sleep "$warmup_delay"
|
||||||
while [[ "$attempt" -le "$max_attempts" ]]; do
|
while [[ "$attempt" -le "$max_attempts" ]]; do
|
||||||
@@ -584,6 +585,9 @@ run_health_checks() {
|
|||||||
step "探测 HTTP 接口可用性"
|
step "探测 HTTP 接口可用性"
|
||||||
check_http_endpoint "/health"
|
check_http_endpoint "/health"
|
||||||
check_http_endpoint "/api/v1/auth/login-key"
|
check_http_endpoint "/api/v1/auth/login-key"
|
||||||
|
if [[ "$TARGET_ENV" == "dev" ]]; then
|
||||||
|
check_http_endpoint "/" 60
|
||||||
|
fi
|
||||||
ok "HTTP 接口全部就绪"
|
ok "HTTP 接口全部就绪"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user