security: waf 边缘统一注入安全响应头
外部扫描发现站点缺少安全响应头。在 waf 反代/403/502 三个出站路径统一注入 XFO/nosniff/Referrer-Policy/Permissions-Policy/HSTS/CSP,下游同名头一律覆盖避免重复;static 补上漏调用的 apply_security_headers 做纵深防御。
This commit is contained in:
1 parent
e89578fa82
commit
b2ddd9e7e3
2 files changed
+25
No files matched your search
@@ -13,6 +13,7 @@ import sys
|
|||||||
# pymsg 日志客户端(集中式日志 → dbv2 Logs 表)
|
# pymsg 日志客户端(集中式日志 → dbv2 Logs 表)
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
from log_client import logs # noqa: E402
|
from log_client import logs # noqa: E402
|
||||||
|
from security_middleware import apply_security_headers # noqa: E402
|
||||||
|
|
||||||
# 基准目录:编译后取可执行文件所在目录,开发时取脚本所在目录
|
# 基准目录:编译后取可执行文件所在目录,开发时取脚本所在目录
|
||||||
# 解决 Nuitka onefile 解压到 /tmp 后相对路径失效的问题
|
# 解决 Nuitka onefile 解压到 /tmp 后相对路径失效的问题
|
||||||
@@ -38,6 +39,7 @@ args = parser.parse_args()
|
|||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app, origins=["https://qsont.me", "https://yhdet.top", "http://localhost:9200"])
|
CORS(app, origins=["https://qsont.me", "https://yhdet.top", "http://localhost:9200"])
|
||||||
|
apply_security_headers(app)
|
||||||
|
|
||||||
# ==================== 配置文件加载 ====================
|
# ==================== 配置文件加载 ====================
|
||||||
CONFIG_FILE = args.config
|
CONFIG_FILE = args.config
|
||||||
|
|||||||
@@ -48,11 +48,33 @@ func New(upstream string, waf *engine.Engine) (*Proxy, error) {
|
|||||||
rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
logclient.Error("waf", fmt.Sprintf("upstream_error path=%s err=%v", r.URL.Path, err),
|
logclient.Error("waf", fmt.Sprintf("upstream_error path=%s err=%v", r.URL.Path, err),
|
||||||
map[string]any{"ip": r.RemoteAddr})
|
map[string]any{"ip": r.RemoteAddr})
|
||||||
|
setSecurityHeaders(w.Header())
|
||||||
http.Error(w, "upstream unavailable", http.StatusBadGateway)
|
http.Error(w, "upstream unavailable", http.StatusBadGateway)
|
||||||
}
|
}
|
||||||
|
// 统一注入安全响应头:在边缘单点覆盖所有出站响应。
|
||||||
|
// Header.Set 覆盖下游同名头,不会产生重复字段
|
||||||
|
rp.ModifyResponse = func(res *http.Response) error {
|
||||||
|
setSecurityHeaders(res.Header)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setSecurityHeaders 设置安全响应头基线(与 static/security_middleware.py 保持一致)。
|
||||||
|
// CSP 暂保留 unsafe-inline:forgot.html 与 games/*.html 含内联脚本。
|
||||||
|
// HSTS 半年期、不含子域,站点全量 HTTPS 后可按需收紧。
|
||||||
|
func setSecurityHeaders(h http.Header) {
|
||||||
|
h.Set("X-Frame-Options", "DENY")
|
||||||
|
h.Set("X-Content-Type-Options", "nosniff")
|
||||||
|
h.Set("X-XSS-Protection", "1; mode=block")
|
||||||
|
h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||||
|
h.Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
|
||||||
|
h.Set("Strict-Transport-Security", "max-age=15552000")
|
||||||
|
h.Set("Content-Security-Policy",
|
||||||
|
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "+
|
||||||
|
"img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'")
|
||||||
|
}
|
||||||
|
|
||||||
// ServeHTTP 实现 http.Handler。
|
// ServeHTTP 实现 http.Handler。
|
||||||
// 流程:ProcessRequest → 命中则 403 → 否则转发 → ProcessResponseHeaders。
|
// 流程:ProcessRequest → 命中则 403 → 否则转发 → ProcessResponseHeaders。
|
||||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -126,6 +148,7 @@ func (p *Proxy) block(w http.ResponseWriter, r *http.Request, ruleID int, start
|
|||||||
"ua": r.UserAgent(),
|
"ua": r.UserAgent(),
|
||||||
"cost_ms": time.Since(start).Milliseconds(),
|
"cost_ms": time.Since(start).Milliseconds(),
|
||||||
})
|
})
|
||||||
|
setSecurityHeaders(w.Header())
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
_, _ = w.Write([]byte("blocked by WAF\n"))
|
_, _ = w.Write([]byte("blocked by WAF\n"))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user