feat: add CSP and other security related headers in the oCIS proxy service (#8777)

* feat: add CSP and other security related headers in the oCIS proxy service

* fix: consolidate security related headers - drop middleware.Secure

* fix: use github.com/DeepDiver1975/secure

* fix: acceptance tests

* feat: support env var replacements in csp.yaml
This commit is contained in:
Thomas Müller
2024-04-26 09:10:35 +02:00
committed by GitHub
parent d3415a8c92
commit bdbba929d0
47 changed files with 2357 additions and 36 deletions

116
vendor/github.com/unrolled/secure/cspbuilder/builder.go generated vendored Normal file
View File

@@ -0,0 +1,116 @@
package cspbuilder
import (
"sort"
"strings"
)
const (
// Fetch Directives.
ChildSrc = "child-src"
ConnectSrc = "connect-src"
DefaultSrc = "default-src"
FontSrc = "font-src"
FrameSrc = "frame-src"
ImgSrc = "img-src"
ManifestSrc = "manifest-src"
MediaSrc = "media-src"
ObjectSrc = "object-src"
PrefetchSrc = "prefetch-src"
ScriptSrc = "script-src"
ScriptSrcAttr = "script-src-attr"
ScriptSrcElem = "script-src-elem"
StyleSrc = "style-src"
StyleSrcAttr = "style-src-attr"
StyleSrcElem = "style-src-elem"
WorkerSrc = "worker-src"
// Document Directives.
BaseURI = "base-uri"
Sandbox = "sandbox"
// Navigation directives.
FormAction = "form-action"
FrameAncestors = "frame-ancestors"
NavigateTo = "navigate-to"
// Reporting directives.
ReportURI = "report-uri"
ReportTo = "report-to"
// Other directives.
RequireTrustedTypesFor = "require-trusted-types-for"
TrustedTypes = "trusted-types"
UpgradeInsecureRequests = "upgrade-insecure-requests"
)
type Builder struct {
Directives map[string]([]string)
}
// MustBuild is like Build but panics if an error occurs.
func (builder *Builder) MustBuild() string {
policy, err := builder.Build()
if err != nil {
panic(err)
}
return policy
}
// Build creates a content security policy string from the specified directives.
// If any directive contains invalid values, an error is returned instead.
func (builder *Builder) Build() (string, error) {
var sb strings.Builder
var keys []string
for k := range builder.Directives {
keys = append(keys, k)
}
sort.Strings(keys)
for _, directive := range keys {
if sb.Len() > 0 {
sb.WriteString("; ")
}
switch directive {
case Sandbox:
err := buildDirectiveSandbox(&sb, builder.Directives[directive])
if err != nil {
return "", err
}
case FrameAncestors:
err := buildDirectiveFrameAncestors(&sb, builder.Directives[directive])
if err != nil {
return "", err
}
case ReportTo:
err := buildDirectiveReportTo(&sb, builder.Directives[directive])
if err != nil {
return "", err
}
case RequireTrustedTypesFor:
err := buildDirectiveRequireTrustedTypesFor(&sb, builder.Directives[directive])
if err != nil {
return "", err
}
case TrustedTypes:
err := buildDirectiveTrustedTypes(&sb, builder.Directives[directive])
if err != nil {
return "", err
}
case UpgradeInsecureRequests:
err := buildDirectiveUpgradeInsecureRequests(&sb, builder.Directives[directive])
if err != nil {
return "", err
}
default:
// no special handling of directive values needed
err := buildDirectiveDefault(&sb, directive, builder.Directives[directive])
if err != nil {
return "", err
}
}
}
return sb.String(), nil
}

View File

@@ -0,0 +1,154 @@
package cspbuilder
import (
"fmt"
"regexp"
"strings"
)
func buildDirectiveSandbox(sb *strings.Builder, values []string) error {
if len(values) == 0 {
sb.WriteString(Sandbox)
return nil
}
if len(values) > 1 {
return fmt.Errorf("too many values set for directive %s", Sandbox)
}
sandboxVal := values[0]
switch sandboxVal {
case "allow-downloads-without-user-activation":
case "allow-downloads":
case "allow-forms":
case "allow-modals":
case "allow-orientation-lock":
case "allow-pointer-lock":
case "allow-popups-to-escape-sandbox":
case "allow-popups":
case "allow-presentation":
case "allow-same-origin":
case "allow-scripts":
case "allow-storage-access-by-user-activation":
case "allow-top-navigation-by-user-activation":
case "allow-top-navigation-to-custom-protocols":
case "allow-top-navigation":
default:
return fmt.Errorf("unallowed value %s for directive %s", sandboxVal, Sandbox)
}
sb.WriteString(Sandbox)
sb.WriteString(" ")
sb.WriteString(sandboxVal)
return nil
}
func buildDirectiveFrameAncestors(sb *strings.Builder, values []string) error {
if len(values) == 0 {
return fmt.Errorf("no values set for directive %s", FrameAncestors)
}
sb.WriteString(FrameAncestors)
for _, val := range values {
if strings.HasPrefix(val, "'") && strings.HasSuffix(val, "'") {
switch val {
case "'self'":
case "'none'":
default:
return fmt.Errorf("unallowed value %s for directive %s", val, FrameAncestors)
}
}
sb.WriteString(" ")
sb.WriteString(val)
}
return nil
}
func buildDirectiveReportTo(sb *strings.Builder, values []string) error {
if len(values) == 0 {
return fmt.Errorf("no values set for directive %s", ReportTo)
}
if len(values) > 1 {
return fmt.Errorf("too many values set for directive %s", ReportTo)
}
sb.WriteString(ReportTo)
sb.WriteString(" ")
sb.WriteString(values[0])
return nil
}
func buildDirectiveRequireTrustedTypesFor(sb *strings.Builder, values []string) error {
const allowedValue = "'script'"
if len(values) != 1 || values[0] != allowedValue {
return fmt.Errorf("value for directive %s must be %s", RequireTrustedTypesFor, allowedValue)
}
sb.WriteString(RequireTrustedTypesFor)
sb.WriteString(" ")
sb.WriteString(values[0])
return nil
}
func buildDirectiveTrustedTypes(sb *strings.Builder, values []string) error {
sb.WriteString(TrustedTypes)
for _, val := range values {
sb.WriteString(" ")
switch val {
case "'none'":
if len(values) != 1 {
return fmt.Errorf("'none' must be only value for directive %s", TrustedTypes)
}
case "'allow-duplicates'":
// nothing to do
case "*":
// nothing to do
default:
// value is policy name
regex := regexp.MustCompile(`^[A-Za-z0-9\-#=_/@\.%]*$`)
if !regex.MatchString(val) {
return fmt.Errorf("unallowed value %s for directive %s", val, TrustedTypes)
}
}
sb.WriteString(val)
}
return nil
}
func buildDirectiveUpgradeInsecureRequests(sb *strings.Builder, values []string) error {
if len(values) != 0 {
return fmt.Errorf("directive %s must not contain values", UpgradeInsecureRequests)
}
sb.WriteString(UpgradeInsecureRequests)
return nil
}
func buildDirectiveDefault(sb *strings.Builder, directive string, values []string) error {
if len(values) == 0 {
return fmt.Errorf("no values set for directive %s", directive)
}
sb.WriteString(directive)
for i := range values {
sb.WriteString(" ")
sb.WriteString(values[i])
}
return nil
}