完善生产健康检查与前端安全配置

This commit is contained in:
Scout开发者 2026-08-31 00:13:15 +08:00
parent ce1a232208
commit 5a592cf250
5 changed files with 44 additions and 1 deletions

View File

@ -1,6 +1,14 @@
http://packages.example.com { http://packages.example.com {
encode gzip encode gzip
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'"
}
handle /api/v1/updates/stream { handle /api/v1/updates/stream {
reverse_proxy scout:8080 { reverse_proxy scout:8080 {
flush_interval -1 flush_interval -1

View File

@ -10,6 +10,11 @@ server {
index index.html; index index.html;
client_max_body_size 512m; client_max_body_size 512m;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'" always;
location /api/v1/updates/stream { location /api/v1/updates/stream {
proxy_pass http://scout:8080; proxy_pass http://scout:8080;

View File

@ -24,6 +24,10 @@ func (s *Server) hostGuard(next http.Handler) http.Handler {
return next return next
} }
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path == "/healthz" && isLocalRequest(request) {
next.ServeHTTP(writer, request)
return
}
if len(s.AllowedHosts) > 0 && !hostAllowed(request.Host, s.AllowedHosts) { if len(s.AllowedHosts) > 0 && !hostAllowed(request.Host, s.AllowedHosts) {
writeError(writer, http.StatusMisdirectedRequest, "请求域名不在允许列表中") writeError(writer, http.StatusMisdirectedRequest, "请求域名不在允许列表中")
return return
@ -36,6 +40,16 @@ func (s *Server) hostGuard(next http.Handler) http.Handler {
}) })
} }
func isLocalRequest(request *http.Request) bool {
value := strings.TrimSpace(request.RemoteAddr)
host, _, err := net.SplitHostPort(value)
if err != nil {
host = value
}
ip := net.ParseIP(strings.Trim(host, "[]"))
return ip != nil && ip.IsLoopback()
}
func (s *Server) rateLimit(next http.Handler) http.Handler { func (s *Server) rateLimit(next http.Handler) http.Handler {
if s.RateLimitPerMinute <= 0 { if s.RateLimitPerMinute <= 0 {
return next return next

View File

@ -33,6 +33,20 @@ func TestHostGuardRejectsUnknownHost(t *testing.T) {
} }
} }
func TestLocalHealthCheckBypassesProxyGuards(t *testing.T) {
server := (&Server{
AllowedHosts: []string{"packages.example.com"},
ForceHTTPS: true,
}).Handler()
request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:8080/healthz", nil)
request.RemoteAddr = "127.0.0.1:34567"
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("本机健康检查状态错误: %d", response.Code)
}
}
func TestRateLimit(t *testing.T) { func TestRateLimit(t *testing.T) {
server := (&Server{RateLimitPerMinute: 1}).Handler() server := (&Server{RateLimitPerMinute: 1}).Handler()
first := httptest.NewRequest(http.MethodGet, "/healthz", nil) first := httptest.NewRequest(http.MethodGet, "/healthz", nil)

View File

@ -287,7 +287,7 @@ chmod 600 /etc/scout/scout.env
```bash ```bash
cp deploy/scout.env.example deploy/scout.env cp deploy/scout.env.example deploy/scout.env
# 编辑 deploy/scout.env,替换所有生产占位符 # 编辑 deploy/scout.env,替换所有生产占位符
docker compose -f deploy/docker-compose.yml up -d --build docker compose -f deploy/docker-compose.yml up -d --build
``` ```
Compose 会启动 Scout API 和前端静态站点: Compose 会启动 Scout API 和前端静态站点:
@ -339,6 +339,8 @@ sudo journalctl -u scout.service -f
外部网关终止 HTTPS 后,必须将 `X-Forwarded-Proto: https` 传给 Scout;否则生产配置中的 `SCOUT_FORCE_HTTPS=true` 会拒绝请求。这是协议边界检查,不是 Scout 的证书管理功能。 外部网关终止 HTTPS 后,必须将 `X-Forwarded-Proto: https` 传给 Scout;否则生产配置中的 `SCOUT_FORCE_HTTPS=true` 会拒绝请求。这是协议边界检查,不是 Scout 的证书管理功能。
`deploy/Caddyfile` 仅是 Caddy 反向代理的参考配置,其中的示例主机名需要替换为实际访问地址。它不会申请或配置 SSL 证书,也不会执行 DNS 解析、域名绑定或服务商 API 操作;如果 SSL 和 DNS 已由其他服务处理,请继续让该服务负责 TLS 终止,并把请求转发到 Scout 的入口。
### 健康检查、备份和升级 ### 健康检查、备份和升级
健康检查接口: 健康检查接口: