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
-1
View File
@@ -79,7 +79,6 @@ func Server(opts ...Option) (http.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
),
middleware.Secure,
}
// how do we secure the api?
var requireAdminMiddleware func(stdhttp.Handler) stdhttp.Handler
-1
View File
@@ -61,7 +61,6 @@ func Server(opts ...Option) (http.Service, error) {
chimiddleware.RequestID,
middleware.TraceContext,
middleware.NoCache,
middleware.Secure,
middleware.Version(
options.Config.Service.Name,
version.GetString(),
@@ -55,7 +55,6 @@ func Server(opts ...Option) (ohttp.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
))
mux.Use(middleware.Secure)
mux.Use(middleware.Version(
options.Name,
-1
View File
@@ -69,7 +69,6 @@ func Server(opts ...Option) (http.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
),
middleware.Secure,
middleware.Version(
options.Config.Service.Name,
version.GetString(),
+6
View File
@@ -300,6 +300,11 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config,
Now: time.Now,
})
cspConfig, err := middleware.LoadCSPConfig(cfg)
if err != nil {
logger.Fatal().Err(err).Msg("Failed to load CSP configuration.")
}
return alice.New(
// first make sure we log all requests and redirect to https if necessary
otelhttp.NewMiddleware("proxy",
@@ -315,6 +320,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config,
chimiddleware.RequestID,
middleware.AccessLog(logger),
middleware.HTTPSRedirect,
middleware.Security(cspConfig),
router.Middleware(cfg.PolicySelector, cfg.Policies, logger),
middleware.Authentication(
authenticators,
+2 -1
View File
@@ -42,6 +42,7 @@ type Config struct {
BackendHTTPSCACert string `yaml:"backend_https_cacert" env:"PROXY_HTTPS_CACERT" desc:"Path/File for the root CA certificate used to validate the servers TLS certificate for https enabled backend services." introductionVersion:"pre5.0"`
AuthMiddleware AuthMiddleware `yaml:"auth_middleware"`
PoliciesMiddleware PoliciesMiddleware `yaml:"policies_middleware"`
CSPConfigFileLocation string `yaml:"csp_config_file_location" env:"PROXY_CSP_CONFIG_FILE_LOCATION" desc:"The location of the CSP configuration file." introductionVersion:"6.0"`
Context context.Context `yaml:"-" json:"-"`
}
@@ -140,7 +141,7 @@ type RoleAssignment struct {
OIDCRoleMapper OIDCRoleMapper `yaml:"oidc_role_mapper"`
}
// OIDCRoleMapper contains the configuration for the "oidc" role assignment driber
// OIDCRoleMapper contains the configuration for the "oidc" role assignment driver
type OIDCRoleMapper struct {
RoleClaim string `yaml:"role_claim" env:"PROXY_ROLE_ASSIGNMENT_OIDC_CLAIM" desc:"The OIDC claim used to create the users role assignment." introductionVersion:"pre5.0"`
RolesMap []RoleMapping `yaml:"role_mapping" desc:"A list of mappings of ocis role names to PROXY_ROLE_ASSIGNMENT_OIDC_CLAIM claim values. This setting can only be configured in the configuration file and not via environment variables."`
+13
View File
@@ -0,0 +1,13 @@
package config
import (
_ "embed"
)
// CSP defines CSP header directives
type CSP struct {
Directives map[string][]string `yaml:"directives"`
}
//go:embed csp.yaml
var DefaultCSPConfig string
+31
View File
@@ -0,0 +1,31 @@
directives:
child-src:
- '''self'''
connect-src:
- '''self'''
default-src:
- '''none'''
font-src:
- '''self'''
frame-ancestors:
- '''none'''
frame-src:
- '''self'''
- 'https://embed.diagrams.net/'
img-src:
- '''self'''
- 'data:'
- 'blob:'
manifest-src:
- '''self'''
media-src:
- '''self'''
object-src:
- '''self'''
- 'blob:'
script-src:
- '''self'''
- '''unsafe-inline'''
style-src:
- '''self'''
- '''unsafe-inline'''
@@ -86,6 +86,7 @@ func DefaultConfig() *config.Config {
AutoprovisionAccounts: false,
EnableBasicAuth: false,
InsecureBackends: false,
CSPConfigFileLocation: "",
}
}
+61
View File
@@ -0,0 +1,61 @@
package middleware
import (
"github.com/a8m/envsubst"
"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
"github.com/unrolled/secure"
"github.com/unrolled/secure/cspbuilder"
"gopkg.in/yaml.v2"
"net/http"
"os"
)
// LoadCSPConfig loads CSP header configuration from a yaml file.
func LoadCSPConfig(proxyCfg *config.Config) (*config.CSP, error) {
yamlContent, err := loadCSPYaml(proxyCfg)
if err != nil {
return nil, err
}
// replace env vars ..
yamlContent, err = envsubst.Bytes(yamlContent)
if err != nil {
return nil, err
}
// read yaml
cspConfig := config.CSP{}
err = yaml.Unmarshal(yamlContent, &cspConfig)
if err != nil {
return nil, err
}
return &cspConfig, nil
}
func loadCSPYaml(proxyCfg *config.Config) ([]byte, error) {
if proxyCfg.CSPConfigFileLocation == "" {
return []byte(config.DefaultCSPConfig), nil
}
return os.ReadFile(proxyCfg.CSPConfigFileLocation)
}
// Security is a middleware to apply security relevant http headers like CSP.
func Security(cspConfig *config.CSP) func(h http.Handler) http.Handler {
cspBuilder := cspbuilder.Builder{
Directives: cspConfig.Directives,
}
secureMiddleware := secure.New(secure.Options{
BrowserXssFilter: true,
ContentSecurityPolicy: cspBuilder.MustBuild(),
ContentTypeNosniff: true,
CustomFrameOptionsValue: "SAMEORIGIN",
FrameDeny: true,
ReferrerPolicy: "strict-origin-when-cross-origin",
STSSeconds: 315360000,
STSPreload: true,
})
return func(next http.Handler) http.Handler {
return secureMiddleware.Handler(next)
}
}
@@ -52,7 +52,6 @@ func Server(opts ...Option) (ohttp.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
))
mux.Use(middleware.Secure)
mux.Use(middleware.ExtractAccountUUID(
account.Logger(options.Logger),
account.JWTSecret(options.Config.TokenManager.JWTSecret)),
-1
View File
@@ -64,7 +64,6 @@ func Server(opts ...Option) (http.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
),
middleware.Secure,
}
mux := chi.NewMux()
@@ -39,7 +39,6 @@ func Server(opts ...Option) (http.Service, error) {
svc.Middleware(
middleware.RealIP,
middleware.RequestID,
// ocismiddleware.Secure,
ocismiddleware.Version(
options.Config.Service.Name,
version.GetString(),
@@ -63,7 +63,6 @@ func Server(opts ...Option) (http.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
),
middleware.Secure,
}
mux := chi.NewMux()
@@ -7,7 +7,6 @@ import (
// SilentRefresh allows the oidc client lib to silently refresh the token in an iframe
func SilentRefresh(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'")
next.ServeHTTP(w, r)
})
-1
View File
@@ -84,7 +84,6 @@ func Server(opts ...Option) (http.Service, error) {
chimiddleware.RealIP,
chimiddleware.RequestID,
middleware.NoCache,
middleware.Secure,
webmid.SilentRefresh,
middleware.Version(
"web",
@@ -48,7 +48,6 @@ func Server(opts ...Option) (http.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
),
middleware.Secure,
middleware.Version(
options.Config.Service.Name,
version.GetString(),
@@ -57,7 +57,6 @@ func Server(opts ...Option) (ohttp.Service, error) {
cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
))
mux.Use(middleware.Secure)
mux.Use(middleware.Version(
options.Name,