完善生产健康检查与前端安全配置
This commit is contained in:
parent
ce1a232208
commit
5a592cf250
@ -1,6 +1,14 @@
|
||||
http://packages.example.com {
|
||||
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 {
|
||||
reverse_proxy scout:8080 {
|
||||
flush_interval -1
|
||||
|
||||
@ -10,6 +10,11 @@ server {
|
||||
index index.html;
|
||||
|
||||
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 {
|
||||
proxy_pass http://scout:8080;
|
||||
|
||||
@ -24,6 +24,10 @@ func (s *Server) hostGuard(next http.Handler) http.Handler {
|
||||
return next
|
||||
}
|
||||
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) {
|
||||
writeError(writer, http.StatusMisdirectedRequest, "请求域名不在允许列表中")
|
||||
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 {
|
||||
if s.RateLimitPerMinute <= 0 {
|
||||
return next
|
||||
|
||||
@ -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) {
|
||||
server := (&Server{RateLimitPerMinute: 1}).Handler()
|
||||
first := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
|
||||
4
使用说明.md
4
使用说明.md
@ -287,7 +287,7 @@ chmod 600 /etc/scout/scout.env
|
||||
```bash
|
||||
cp deploy/scout.env.example 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 和前端静态站点:
|
||||
@ -339,6 +339,8 @@ sudo journalctl -u scout.service -f
|
||||
|
||||
外部网关终止 HTTPS 后,必须将 `X-Forwarded-Proto: https` 传给 Scout;否则生产配置中的 `SCOUT_FORCE_HTTPS=true` 会拒绝请求。这是协议边界检查,不是 Scout 的证书管理功能。
|
||||
|
||||
`deploy/Caddyfile` 仅是 Caddy 反向代理的参考配置,其中的示例主机名需要替换为实际访问地址。它不会申请或配置 SSL 证书,也不会执行 DNS 解析、域名绑定或服务商 API 操作;如果 SSL 和 DNS 已由其他服务处理,请继续让该服务负责 TLS 终止,并把请求转发到 Scout 的入口。
|
||||
|
||||
### 健康检查、备份和升级
|
||||
|
||||
健康检查接口:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user