mirror of
https://github.com/Luzifer/go_helpers.git
synced 2024-12-24 13:01:21 +00:00
Add CSP helper
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
6aae1097dd
commit
fe36f52937
2 changed files with 126 additions and 0 deletions
93
http/csp.go
Normal file
93
http/csp.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// CSP is a non-concurrency-safe map to hold a Content-Security-Policy
|
||||
// and manipulate it afterwards to eventually render it into its
|
||||
// header-representation
|
||||
CSP map[string][]CSPSourceValue
|
||||
// CSPHashAlgo defines the available hash algorithms
|
||||
CSPHashAlgo string
|
||||
// CSPSourceValue represents an value in the map for a given directive
|
||||
CSPSourceValue string
|
||||
)
|
||||
|
||||
// Collection of pre-defined values. For documentation see
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources
|
||||
const (
|
||||
CSPHashSHA256 CSPHashAlgo = "sha256"
|
||||
CSPHashSHA384 CSPHashAlgo = "sha384"
|
||||
CSPHashSHA512 CSPHashAlgo = "sha512"
|
||||
|
||||
CSPSrcNone CSPSourceValue = "'none'"
|
||||
CSPSrcReportSample CSPSourceValue = "'report-sample'"
|
||||
CSPSrcSelf CSPSourceValue = "'self'"
|
||||
CSPSrcStrictDynamic CSPSourceValue = "'strict-dynamic'"
|
||||
CSPSrcUnsafeEval CSPSourceValue = "'unsafe-eval'"
|
||||
CSPSrcUnsafeHashes CSPSourceValue = "'unsafe-hashes'"
|
||||
CSPSrcUnsafeInline CSPSourceValue = "'unsafe-inline'"
|
||||
CSPSrcWASMUnsafeEval CSPSourceValue = "'wasm-unsafe-eval'"
|
||||
|
||||
CSPSrcSchemeData CSPSourceValue = "data:"
|
||||
CSPSrcSchemeMediastream CSPSourceValue = "mediastream:"
|
||||
CSPSrcSchemeBlob CSPSourceValue = "blob:"
|
||||
CSPSrcSchemeFilesystem CSPSourceValue = "filesystem:"
|
||||
)
|
||||
|
||||
// CSPSrcHash takes an algo (sha256, sha384 or sha512) and the sum
|
||||
// value and converts it into the right representation for the header
|
||||
func CSPSrcHash(algo CSPHashAlgo, sum []byte) CSPSourceValue {
|
||||
return CSPSourceValue(fmt.Sprintf("'%s-%s'", algo, base64.StdEncoding.EncodeToString(sum)))
|
||||
}
|
||||
|
||||
// CSPSrcNonce takes a base64 encoded nonce value and converts it
|
||||
// into the right representation for the header
|
||||
func CSPSrcNonce(b64Value string) CSPSourceValue {
|
||||
return CSPSourceValue(fmt.Sprintf("'nonce-%s'", b64Value))
|
||||
}
|
||||
|
||||
// Add adds a single CSPSourceValue to the given directive
|
||||
func (c CSP) Add(directive string, value CSPSourceValue) {
|
||||
c[directive] = append(c[directive], value)
|
||||
}
|
||||
|
||||
// Clone copies the CSP into a new one for manipulation
|
||||
func (c CSP) Clone() CSP {
|
||||
n := make(CSP)
|
||||
for dir, vals := range c {
|
||||
n[dir] = append([]CSPSourceValue{}, vals...)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Set replaces all present values for the given directive
|
||||
func (c CSP) Set(directive string, value CSPSourceValue) {
|
||||
c[directive] = []CSPSourceValue{value}
|
||||
}
|
||||
|
||||
// ToHeaderValue encodes the policy into the format expected in the
|
||||
// `Content-Security-Policy` header.
|
||||
func (c CSP) ToHeaderValue() string {
|
||||
var keys []string
|
||||
for k := range c {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var parts []string
|
||||
for _, k := range keys {
|
||||
lst := []string{k}
|
||||
for _, v := range c[k] {
|
||||
lst = append(lst, string(v))
|
||||
}
|
||||
parts = append(parts, strings.Join(lst, " "))
|
||||
}
|
||||
|
||||
return strings.Join(parts, ";")
|
||||
}
|
33
http/csp_test.go
Normal file
33
http/csp_test.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCSPClone(t *testing.T) {
|
||||
c1 := CSP{"default-src": []CSPSourceValue{CSPSrcNone}}
|
||||
c2 := c1.Clone()
|
||||
|
||||
c2.Add("default-src", CSPSrcSelf) // Makes no sense in real world!
|
||||
|
||||
assert.NotEqual(t, c1["default-src"], c2["default-src"], "expect c1 not to have changed")
|
||||
}
|
||||
|
||||
func TestCSPToHeaderValue(t *testing.T) {
|
||||
c := CSP{}
|
||||
c.Set("default-src", CSPSrcNone)
|
||||
|
||||
c.Set("connect-src", CSPSrcSelf)
|
||||
c.Set("font-src", CSPSrcSelf)
|
||||
c.Set("img-src", CSPSrcSelf)
|
||||
c.Add("img-src", CSPSrcSchemeData)
|
||||
c.Set("script-src", CSPSrcSelf)
|
||||
c.Add("script-src", CSPSrcUnsafeInline)
|
||||
c.Set("style-src", CSPSrcSelf)
|
||||
|
||||
assert.Equal(t,
|
||||
"connect-src 'self';default-src 'none';font-src 'self';img-src 'self' data:;script-src 'self' 'unsafe-inline';style-src 'self'",
|
||||
c.ToHeaderValue())
|
||||
}
|
Loading…
Reference in a new issue