mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-05 08:02:14 +00:00
feat: send HSTS from the forms service only on FORMS_DOMAIN and strip it in the installer's Caddy block for customer-owned domains, so no customer apex is pinned to HTTPS
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -48,13 +49,14 @@ func SecurityHeaders() gin.HandlerFunc {
|
||||
|
||||
// PageHeaders is the part of SecurityHeaders a service serving framable pages
|
||||
// can carry (the forms service): no content sniffing, a referrer policy, and
|
||||
// HSTS over TLS. Each page sets its own CSP.
|
||||
func PageHeaders() gin.HandlerFunc {
|
||||
// HSTS over TLS on ownHost only, never on a customer's domain. Each page sets its own CSP.
|
||||
func PageHeaders(ownHost string) gin.HandlerFunc {
|
||||
ownHost = strings.ToLower(ownHost)
|
||||
return func(c *gin.Context) {
|
||||
h := c.Writer.Header()
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
if requestIsHTTPS(c) {
|
||||
if ownHost != "" && requestIsHTTPS(c) && strings.EqualFold(requestHostname(c.Request.Host), ownHost) {
|
||||
h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||
}
|
||||
c.Next()
|
||||
@@ -79,3 +81,11 @@ func requestIsHTTPS(c *gin.Context) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// requestHostname is the request's Host without a port.
|
||||
func requestHostname(host string) string {
|
||||
if h, _, err := net.SplitHostPort(host); err == nil {
|
||||
return h
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/warmbly/warmbly/internal/api/middleware"
|
||||
"github.com/warmbly/warmbly/internal/config"
|
||||
"github.com/warmbly/warmbly/internal/formwire"
|
||||
)
|
||||
|
||||
@@ -138,7 +140,7 @@ func (s *Server) Router(trustedProxies []string) (*gin.Engine, error) {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
r := gin.New()
|
||||
r.Use(middleware.RequestLogger(), gin.Recovery(), middleware.PageHeaders())
|
||||
r.Use(middleware.RequestLogger(), gin.Recovery(), middleware.PageHeaders(formsHost()))
|
||||
// Same posture as the backend: trust no proxy unless the operator names
|
||||
// it, so a forged X-Forwarded-For cannot dodge the submit limiter.
|
||||
if len(trustedProxies) > 0 {
|
||||
@@ -433,3 +435,12 @@ func (s *Server) ServeFormsEmbedJS(c *gin.Context) {
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.Data(http.StatusOK, "application/javascript; charset=utf-8", formsEmbedJS)
|
||||
}
|
||||
|
||||
// formsHost is the hostname of FORMS_DOMAIN, the one host the forms service sends HSTS for.
|
||||
func formsHost() string {
|
||||
u, err := url.Parse(config.FormsBaseURL())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
@@ -1604,6 +1604,7 @@ render_caddyfile() {
|
||||
header {
|
||||
X-Content-Type-Options "nosniff"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
-Strict-Transport-Security
|
||||
-Server
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
098221eed28b874d84250518a001bbf38e3e74520c00bc48d3cc1328d2107f56 install.sh
|
||||
d68bebcc66b502d4f78736a1325517aea199e7f3fdc8c517f9d4165ff7985116 install.sh
|
||||
|
||||
Reference in New Issue
Block a user