From 09c4800143d75d5cc385e8a667277e324e762f20 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 31 Jan 2024 00:57:47 +0800 Subject: [PATCH] :sparkles: Add Helmet & Watermark --- config/example.toml | 3 +- pkg/hypertext/proxies.go | 17 -------- pkg/navi/helmet.go | 89 ++++++++++++++++++++++++++++++++++++++++ pkg/navi/route.go | 40 ++++++++++++++++-- pkg/navi/struct.go | 1 + 5 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 pkg/navi/helmet.go diff --git a/config/example.toml b/config/example.toml index 117371c..9117a1c 100644 --- a/config/example.toml +++ b/config/example.toml @@ -28,4 +28,5 @@ host = ["localhost:8000"] path = ["/"] [[locations.destinations]] id = "example-destination" -uri = "http://protocol.smartsheep.studio:20004" \ No newline at end of file +uri = "https://example.com" +helmet = { x_frame_options = "SAMEORIGIN" } \ No newline at end of file diff --git a/pkg/hypertext/proxies.go b/pkg/hypertext/proxies.go index 18dc230..8b0c15a 100644 --- a/pkg/hypertext/proxies.go +++ b/pkg/hypertext/proxies.go @@ -1,7 +1,6 @@ package hypertext import ( - "fmt" "github.com/spf13/viper" "math/rand" "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 err := navi.R.Forward(c, dest) diff --git a/pkg/navi/helmet.go b/pkg/navi/helmet.go new file mode 100644 index 0000000..54c29de --- /dev/null +++ b/pkg/navi/helmet.go @@ -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) + } +} diff --git a/pkg/navi/route.go b/pkg/navi/route.go index fd4bd03..f23aa33 100644 --- a/pkg/navi/route.go +++ b/pkg/navi/route.go @@ -1,7 +1,10 @@ package navi import ( + roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" "code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers" + "fmt" + "github.com/spf13/viper" "github.com/gofiber/fiber/v2" ) @@ -11,15 +14,44 @@ type RoadApp struct { 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() { case DestinationHypertext: - return makeUnifiedResponse(ctx, dest) + err = makeUnifiedResponse(c, dest) case DestinationStaticFile: - return makeFileResponse(ctx, dest) + err = makeFileResponse(c, dest) 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 diff --git a/pkg/navi/struct.go b/pkg/navi/struct.go index 4127918..f3166c9 100644 --- a/pkg/navi/struct.go +++ b/pkg/navi/struct.go @@ -38,6 +38,7 @@ const ( type Destination struct { ID string `json:"id" toml:"id"` Uri string `json:"uri" toml:"uri"` + Helmet *HelmetConfig `json:"helmet" toml:"helmet"` Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"` }