Add Helmet & Watermark
All checks were successful
release-nightly / build-docker (push) Successful in 2m37s

This commit is contained in:
LittleSheep 2024-01-31 00:57:47 +08:00
parent ef10fab47d
commit 09c4800143
5 changed files with 128 additions and 22 deletions

View File

@ -28,4 +28,5 @@ host = ["localhost:8000"]
path = ["/"] path = ["/"]
[[locations.destinations]] [[locations.destinations]]
id = "example-destination" id = "example-destination"
uri = "http://protocol.smartsheep.studio:20004" uri = "https://example.com"
helmet = { x_frame_options = "SAMEORIGIN" }

View File

@ -1,7 +1,6 @@
package hypertext package hypertext
import ( import (
"fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"math/rand" "math/rand"
"regexp" "regexp"
@ -105,22 +104,6 @@ func makeResponse(c *fiber.Ctx, region *navi.Region, location *navi.Location, de
} }
} }
// Add reserve proxy headers
ip := c.IP()
scheme := c.Protocol()
protocol := string(c.Request().Header.Protocol())
c.Request().Header.Set(fiber.HeaderXForwardedFor, ip)
c.Request().Header.Set(fiber.HeaderXForwardedHost, ip)
c.Request().Header.Set(fiber.HeaderXForwardedProto, scheme)
c.Request().Header.Set(
fiber.HeaderVia,
fmt.Sprintf("%s %s", protocol, viper.GetString("central")),
)
c.Request().Header.Set(
fiber.HeaderForwarded,
fmt.Sprintf("by=%s; for=%s; host=%s; proto=%s", c.IP(), c.IP(), c.Get(fiber.HeaderHost), scheme),
)
// Forward // Forward
err := navi.R.Forward(c, dest) err := navi.R.Forward(c, dest)

89
pkg/navi/helmet.go Normal file
View File

@ -0,0 +1,89 @@
package navi
import (
"fmt"
"github.com/gofiber/fiber/v2"
)
type HelmetConfig struct {
XSSProtection string `json:"xss_protection" toml:"xss_protection"`
ContentTypeNosniff string `json:"content_type_nosniff" toml:"content_type_nosniff"`
XFrameOptions string `json:"x_frame_options" toml:"x_frame_options"`
HSTSMaxAge int `json:"hsts_max_age" toml:"hsts_max_age"`
HSTSExcludeSubdomains bool `json:"hsts_exclude_subdomains" toml:"hsts_exclude_subdomains"`
ContentSecurityPolicy string `json:"content_security_policy" toml:"content_security_policy"`
CSPReportOnly bool `json:"csp_report_only" toml:"csp_report_only"`
HSTSPreloadEnabled bool `json:"hsts_preload_enabled" toml:"hsts_preload_enabled"`
ReferrerPolicy string `json:"referrer_policy" toml:"referrer_policy"`
PermissionPolicy string `json:"permission_policy" toml:"permission_policy"`
CrossOriginEmbedderPolicy string `json:"cross_origin_embedder_policy" toml:"cross_origin_embedder_policy"`
CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" toml:"cross_origin_opener_policy"`
CrossOriginResourcePolicy string `json:"cross_origin_resource_policy" toml:"cross_origin_resource_policy"`
OriginAgentCluster string `json:"origin_agent_cluster" toml:"origin_agent_cluster"`
XDNSPrefetchControl string `json:"xdns_prefetch_control" toml:"xdns_prefetch_control"`
XDownloadOptions string `json:"x_download_options" toml:"x_download_options"`
XPermittedCrossDomain string `json:"x_permitted_cross_domain" toml:"x_permitted_cross_domain"`
}
func (cfg HelmetConfig) Apply(c *fiber.Ctx) {
// Apply other headers
if cfg.XSSProtection != "" {
c.Set(fiber.HeaderXXSSProtection, cfg.XSSProtection)
}
if cfg.ContentTypeNosniff != "" {
c.Set(fiber.HeaderXContentTypeOptions, cfg.ContentTypeNosniff)
}
if cfg.XFrameOptions != "" {
c.Set(fiber.HeaderXFrameOptions, cfg.XFrameOptions)
}
if cfg.CrossOriginEmbedderPolicy != "" {
c.Set("Cross-Origin-Embedder-Policy", cfg.CrossOriginEmbedderPolicy)
}
if cfg.CrossOriginOpenerPolicy != "" {
c.Set("Cross-Origin-Opener-Policy", cfg.CrossOriginOpenerPolicy)
}
if cfg.CrossOriginResourcePolicy != "" {
c.Set("Cross-Origin-Resource-Policy", cfg.CrossOriginResourcePolicy)
}
if cfg.OriginAgentCluster != "" {
c.Set("Origin-Agent-Cluster", cfg.OriginAgentCluster)
}
if cfg.ReferrerPolicy != "" {
c.Set("Referrer-Policy", cfg.ReferrerPolicy)
}
if cfg.XDNSPrefetchControl != "" {
c.Set("X-DNS-Prefetch-Control", cfg.XDNSPrefetchControl)
}
if cfg.XDownloadOptions != "" {
c.Set("X-Download-Options", cfg.XDownloadOptions)
}
if cfg.XPermittedCrossDomain != "" {
c.Set("X-Permitted-Cross-Domain-Policies", cfg.XPermittedCrossDomain)
}
// Handle HSTS headers
if c.Protocol() == "https" && cfg.HSTSMaxAge != 0 {
subdomains := ""
if !cfg.HSTSExcludeSubdomains {
subdomains = "; includeSubDomains"
}
if cfg.HSTSPreloadEnabled {
subdomains = fmt.Sprintf("%s; preload", subdomains)
}
c.Set(fiber.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", cfg.HSTSMaxAge, subdomains))
}
// Handle Content-Security-Policy headers
if cfg.ContentSecurityPolicy != "" {
if cfg.CSPReportOnly {
c.Set(fiber.HeaderContentSecurityPolicyReportOnly, cfg.ContentSecurityPolicy)
} else {
c.Set(fiber.HeaderContentSecurityPolicy, cfg.ContentSecurityPolicy)
}
}
// Handle Permissions-Policy headers
if cfg.PermissionPolicy != "" {
c.Set(fiber.HeaderPermissionsPolicy, cfg.PermissionPolicy)
}
}

View File

@ -1,7 +1,10 @@
package navi package navi
import ( import (
roadsign "code.smartsheep.studio/goatworks/roadsign/pkg"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers" "code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
"fmt"
"github.com/spf13/viper"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -11,15 +14,44 @@ type RoadApp struct {
Traces []RoadTrace `json:"traces"` Traces []RoadTrace `json:"traces"`
} }
func (v *RoadApp) Forward(ctx *fiber.Ctx, dest *Destination) error { func (v *RoadApp) Forward(c *fiber.Ctx, dest *Destination) error {
// Add reserve proxy headers
ip := c.IP()
scheme := c.Protocol()
protocol := string(c.Request().Header.Protocol())
c.Request().Header.Set(fiber.HeaderXForwardedFor, ip)
c.Request().Header.Set(fiber.HeaderXForwardedHost, ip)
c.Request().Header.Set(fiber.HeaderXForwardedProto, scheme)
c.Request().Header.Set(
fiber.HeaderVia,
fmt.Sprintf("%s %s", protocol, viper.GetString("central")),
)
c.Request().Header.Set(
fiber.HeaderForwarded,
fmt.Sprintf("by=%s; for=%s; host=%s; proto=%s", c.IP(), c.IP(), c.Get(fiber.HeaderHost), scheme),
)
// Response body
var err error
switch dest.GetType() { switch dest.GetType() {
case DestinationHypertext: case DestinationHypertext:
return makeUnifiedResponse(ctx, dest) err = makeUnifiedResponse(c, dest)
case DestinationStaticFile: case DestinationStaticFile:
return makeFileResponse(ctx, dest) err = makeFileResponse(c, dest)
default: default:
return fiber.ErrBadGateway err = fiber.ErrBadGateway
} }
// Apply helmet
if dest.Helmet != nil {
dest.Helmet.Apply(c)
}
// Apply watermark
c.Response().Header.Set(fiber.HeaderServer, "RoadSign")
c.Response().Header.Set(fiber.HeaderXPoweredBy, fmt.Sprintf("RoadSign %s", roadsign.AppVersion))
return err
} }
type RequestTransformerConfig = transformers.TransformerConfig type RequestTransformerConfig = transformers.TransformerConfig

View File

@ -38,6 +38,7 @@ const (
type Destination struct { type Destination struct {
ID string `json:"id" toml:"id"` ID string `json:"id" toml:"id"`
Uri string `json:"uri" toml:"uri"` Uri string `json:"uri" toml:"uri"`
Helmet *HelmetConfig `json:"helmet" toml:"helmet"`
Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"` Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"`
} }