zitadel/internal/api/http/middleware/csp.go
Livio Spring 632639ae7f
feat: enable iframe use (#4766)
* feat: enable iframe use

* cleanup

* fix mocks

* fix linting

* docs: add iframe usage to solution scenarios configurations

* improve api

* feat(console): security policy

* description

* remove unnecessary line

* disable input button and urls when not enabled

* add image to docs

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
2022-12-14 07:17:36 +01:00

145 lines
3.7 KiB
Go

package middleware
import (
"fmt"
"strings"
)
type CSP struct {
DefaultSrc CSPSourceOptions
ScriptSrc CSPSourceOptions
ObjectSrc CSPSourceOptions
StyleSrc CSPSourceOptions
ImgSrc CSPSourceOptions
MediaSrc CSPSourceOptions
FrameSrc CSPSourceOptions
FrameAncestors CSPSourceOptions
FontSrc CSPSourceOptions
ManifestSrc CSPSourceOptions
ConnectSrc CSPSourceOptions
FormAction CSPSourceOptions
}
var (
DefaultSCP = CSP{
DefaultSrc: CSPSourceOptsNone(),
ScriptSrc: CSPSourceOptsSelf(),
ObjectSrc: CSPSourceOptsNone(),
StyleSrc: CSPSourceOptsSelf(),
ImgSrc: CSPSourceOptsSelf(),
MediaSrc: CSPSourceOptsNone(),
FrameSrc: CSPSourceOptsNone(),
FrameAncestors: CSPSourceOptsNone(),
FontSrc: CSPSourceOptsSelf(),
ManifestSrc: CSPSourceOptsSelf(),
ConnectSrc: CSPSourceOptsSelf(),
}
)
func (csp *CSP) Value(nonce, host string, iframe []string) string {
valuesMap := csp.asMap(iframe)
values := make([]string, 0, len(valuesMap))
for k, v := range valuesMap {
if v == nil {
continue
}
values = append(values, fmt.Sprintf("%v %v", k, v.String(nonce, host)))
}
return strings.Join(values, ";")
}
func (csp *CSP) asMap(iframe []string) map[string]CSPSourceOptions {
frameAncestors := csp.FrameAncestors
if len(iframe) > 0 {
frameAncestors = CSPSourceOpts().AddHost(iframe...)
}
return map[string]CSPSourceOptions{
"default-src": csp.DefaultSrc,
"script-src": csp.ScriptSrc,
"object-src": csp.ObjectSrc,
"style-src": csp.StyleSrc,
"img-src": csp.ImgSrc,
"media-src": csp.MediaSrc,
"frame-src": csp.FrameSrc,
"frame-ancestors": frameAncestors,
"font-src": csp.FontSrc,
"manifest-src": csp.ManifestSrc,
"connect-src": csp.ConnectSrc,
"form-action": csp.FormAction,
}
}
type CSPSourceOptions []string
func CSPSourceOpts() CSPSourceOptions {
return CSPSourceOptions{}
}
func CSPSourceOptsNone() CSPSourceOptions {
return []string{"'none'"}
}
func CSPSourceOptsSelf() CSPSourceOptions {
return []string{"'self'"}
}
func (srcOpts CSPSourceOptions) AddSelf() CSPSourceOptions {
return append(srcOpts, "'self'")
}
func (srcOpts CSPSourceOptions) AddInline() CSPSourceOptions {
return append(srcOpts, "'unsafe-inline'")
}
func (srcOpts CSPSourceOptions) AddEval() CSPSourceOptions {
return append(srcOpts, "'unsafe-eval'")
}
func (srcOpts CSPSourceOptions) AddStrictDynamic() CSPSourceOptions {
return append(srcOpts, "'strict-dynamic'")
}
func (srcOpts CSPSourceOptions) AddHost(h ...string) CSPSourceOptions {
return append(srcOpts, h...)
}
func (srcOpts CSPSourceOptions) AddOwnHost() CSPSourceOptions {
return append(srcOpts, placeHolderHost)
}
func (srcOpts CSPSourceOptions) AddScheme(s ...string) CSPSourceOptions {
return srcOpts.add(s, "%v:")
}
func (srcOpts CSPSourceOptions) AddNonce() CSPSourceOptions {
return append(srcOpts, fmt.Sprintf("'nonce-%s'", placeHolderNonce))
}
const (
placeHolderNonce = "{{nonce}}"
placeHolderHost = "{{host}}"
)
func (srcOpts CSPSourceOptions) AddHash(alg, b64v string) CSPSourceOptions {
return append(srcOpts, fmt.Sprintf("'%v-%v'", alg, b64v))
}
func (srcOpts CSPSourceOptions) String(nonce, host string) string {
value := strings.Join(srcOpts, " ")
if !strings.Contains(value, placeHolderNonce) && !strings.Contains(value, placeHolderHost) {
return value
}
return strings.ReplaceAll(strings.ReplaceAll(value, placeHolderHost, host), placeHolderNonce, nonce)
}
func (srcOpts CSPSourceOptions) add(values []string, format string) CSPSourceOptions {
for i, v := range values {
values[i] = fmt.Sprintf(format, v)
}
return append(srcOpts, values...)
}