aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-10-18 12:45:07 -0700
committerMike Crute <mike@crute.us>2023-10-18 12:45:07 -0700
commit925de3239c9eab61a3a1275a554508f46a172709 (patch)
treed4b3c7d0f4f88c3cbcfc5adb30c296d12a227826
parent4ba637b3c3596d6fb7d073d70f23e4f421949bdd (diff)
downloadgolib-925de3239c9eab61a3a1275a554508f46a172709.tar.bz2
golib-925de3239c9eab61a3a1275a554508f46a172709.tar.xz
golib-925de3239c9eab61a3a1275a554508f46a172709.zip
echo: allow extending CSP at request-timeecho/v0.15.0
-rw-r--r--echo/middleware/csp.go109
1 files changed, 94 insertions, 15 deletions
diff --git a/echo/middleware/csp.go b/echo/middleware/csp.go
index 14047eb..644dfe1 100644
--- a/echo/middleware/csp.go
+++ b/echo/middleware/csp.go
@@ -2,6 +2,8 @@ package middleware
2 2
3import ( 3import (
4 "bytes" 4 "bytes"
5 "crypto/sha256"
6 "encoding/base64"
5 "encoding/json" 7 "encoding/json"
6 "fmt" 8 "fmt"
7 "net/url" 9 "net/url"
@@ -13,7 +15,19 @@ import (
13 "github.com/labstack/echo/v4/middleware" 15 "github.com/labstack/echo/v4/middleware"
14) 16)
15 17
16const HeaderReportTo = "ReportTo" 18const (
19 HeaderReportTo = "ReportTo"
20 cspExtendContextKey = "__echomw_csp__csp_extend"
21 cspReplaceContextKey = "__echomw_csp__csp_replace"
22)
23
24func ReplaceCSP(c echo.Context, csp *ContentSecurityPolicyConfig) {
25 c.Set(cspReplaceContextKey, csp)
26}
27
28func ExtendCSP(c echo.Context, csp *ContentSecurityPolicyConfig) {
29 c.Set(cspExtendContextKey, csp)
30}
17 31
18type ContentSecurityPolicyConfig struct { 32type ContentSecurityPolicyConfig struct {
19 Skipper middleware.Skipper 33 Skipper middleware.Skipper
@@ -46,6 +60,53 @@ type ContentSecurityPolicyConfig struct {
46 RequireTrustedTypesFor []CSPDirective `csp:"require-trusted-types-for"` // experimental 60 RequireTrustedTypesFor []CSPDirective `csp:"require-trusted-types-for"` // experimental
47} 61}
48 62
63func mergeField[T any](a []T, b []T) []T {
64 if b != nil {
65 v := make([]T, len(a)+len(b))
66 copy(v, a)
67 copy(v[len(a):], b)
68 return v
69 } else {
70 return a
71 }
72}
73
74// ExtendSimple returns a copy of the current policy extended with some
75// other policy. Boolean fields are not merged and will retain the value
76// of the base configuration.
77func (c *ContentSecurityPolicyConfig) ExtendSimple(o *ContentSecurityPolicyConfig) *ContentSecurityPolicyConfig {
78 return &ContentSecurityPolicyConfig{
79 Skipper: c.Skipper,
80 ReportOnly: c.ReportOnly,
81 UpgradeInsecureRequests: c.UpgradeInsecureRequests,
82 BlockAllMixedContent: c.BlockAllMixedContent,
83 DefaultSrc: mergeField(c.DefaultSrc, o.DefaultSrc),
84 ChildSrc: mergeField(c.ChildSrc, o.ChildSrc),
85 ConnectSrc: mergeField(c.ConnectSrc, o.ConnectSrc),
86 FontSrc: mergeField(c.FontSrc, o.FontSrc),
87 FrameSrc: mergeField(c.FrameSrc, o.FrameSrc),
88 ImageSrc: mergeField(c.ImageSrc, o.ImageSrc),
89 ManifestSrc: mergeField(c.ManifestSrc, o.ManifestSrc),
90 MediaSrc: mergeField(c.MediaSrc, o.MediaSrc),
91 ObjectSrc: mergeField(c.ObjectSrc, o.ObjectSrc),
92 ScriptSrc: mergeField(c.ScriptSrc, o.ScriptSrc),
93 StyleSrc: mergeField(c.StyleSrc, o.StyleSrc),
94 BaseUri: mergeField(c.BaseUri, o.BaseUri),
95 Sandbox: mergeField(c.Sandbox, o.Sandbox),
96 FormAction: mergeField(c.FormAction, o.FormAction),
97 FrameAncestors: mergeField(c.FrameAncestors, o.FrameAncestors),
98 ReportUri: mergeField(c.ReportUri, o.ReportUri),
99 ReportTo: mergeField(c.ReportTo, o.ReportTo),
100 PrefetchSrc: mergeField(c.PrefetchSrc, o.PrefetchSrc),
101 ScriptSrcElem: mergeField(c.ScriptSrcElem, o.ScriptSrcElem),
102 StyleSrcElem: mergeField(c.StyleSrcElem, o.StyleSrcElem),
103 StyleSrcAttr: mergeField(c.StyleSrcAttr, o.StyleSrcAttr),
104 WorkerSrc: mergeField(c.WorkerSrc, o.WorkerSrc),
105 NavigateTo: mergeField(c.NavigateTo, o.NavigateTo),
106 RequireTrustedTypesFor: mergeField(c.RequireTrustedTypesFor, o.RequireTrustedTypesFor),
107 }
108}
109
49func (c *ContentSecurityPolicyConfig) String() string { 110func (c *ContentSecurityPolicyConfig) String() string {
50 st := reflect.TypeOf(*c) 111 st := reflect.TypeOf(*c)
51 sv := reflect.ValueOf(*c) 112 sv := reflect.ValueOf(*c)
@@ -135,6 +196,11 @@ func CSPShaString(size int, h string) CSPDirective {
135 return CSPDirective(fmt.Sprintf("'sha%d-%s'", size, h)) 196 return CSPDirective(fmt.Sprintf("'sha%d-%s'", size, h))
136} 197}
137 198
199func CSPSha256FromBytes(d []byte) CSPDirective {
200 s := sha256.Sum256(d)
201 return CSPShaString(256, base64.StdEncoding.EncodeToString(s[:]))
202}
203
138type CSPSandbox string 204type CSPSandbox string
139 205
140const ( 206const (
@@ -174,22 +240,35 @@ func ContentSecurityPolicyWithConfig(config ContentSecurityPolicyConfig) echo.Mi
174 return next(c) 240 return next(c)
175 } 241 }
176 242
177 h := c.Response().Header() 243 // This has to hook after the template runs but before the headers
178 if config.ReportOnly { 244 // are written because some template helper functions want to modify
179 h.Set(echo.HeaderContentSecurityPolicyReportOnly, config.String()) 245 // the CSP state and if it renders too early that won't work.
180 } else { 246 c.Response().Before(func() {
181 h.Set(echo.HeaderContentSecurityPolicy, config.String()) 247 liveConfig := &config
182 }
183 248
184 if config.ReportTo != nil { 249 if replace, ok := c.Get(cspReplaceContextKey).(*ContentSecurityPolicyConfig); ok {
185 rt := bytes.Buffer{} 250 liveConfig = replace
186 je := json.NewEncoder(&rt) 251 } else if extend, ok := c.Get(cspExtendContextKey).(*ContentSecurityPolicyConfig); ok {
187 for _, r := range config.ReportTo { 252 liveConfig = config.ExtendSimple(extend)
188 _ = je.Encode(r)
189 rt.WriteString(", ")
190 } 253 }
191 h.Set(HeaderReportTo, rt.String()) 254
192 } 255 h := c.Response().Header()
256 if liveConfig.ReportOnly {
257 h.Set(echo.HeaderContentSecurityPolicyReportOnly, liveConfig.String())
258 } else {
259 h.Set(echo.HeaderContentSecurityPolicy, liveConfig.String())
260 }
261
262 if liveConfig.ReportTo != nil {
263 rt := bytes.Buffer{}
264 je := json.NewEncoder(&rt)
265 for _, r := range liveConfig.ReportTo {
266 _ = je.Encode(r)
267 rt.WriteString(", ")
268 }
269 h.Set(HeaderReportTo, rt.String())
270 }
271 })
193 272
194 return next(c) 273 return next(c)
195 } 274 }