diff --git a/internal/config/config.go b/internal/config/config.go index 7fd8cb9e..39f39777 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -134,21 +134,23 @@ var ( Debug: false, }, Security: httpd.SecurityConf{ - Enabled: false, - AllowedHosts: nil, - AllowedHostsAreRegex: false, - HostsProxyHeaders: nil, - HTTPSRedirect: false, - HTTPSHost: "", - HTTPSProxyHeaders: nil, - STSSeconds: 0, - STSIncludeSubdomains: false, - STSPreload: false, - ContentTypeNosniff: false, - ContentSecurityPolicy: "", - PermissionsPolicy: "", - CrossOriginOpenerPolicy: "", - CacheControl: "", + Enabled: false, + AllowedHosts: nil, + AllowedHostsAreRegex: false, + HostsProxyHeaders: nil, + HTTPSRedirect: false, + HTTPSHost: "", + HTTPSProxyHeaders: nil, + STSSeconds: 0, + STSIncludeSubdomains: false, + STSPreload: false, + ContentTypeNosniff: false, + ContentSecurityPolicy: "", + PermissionsPolicy: "", + CrossOriginOpenerPolicy: "", + CrossOriginResourcePolicy: "", + CrossOriginEmbedderPolicy: "", + CacheControl: "", }, Branding: httpd.Branding{}, } @@ -1565,9 +1567,21 @@ func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) { //nolint: isSet = true } - crossOriginOpenedPolicy, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__CROSS_ORIGIN_OPENER_POLICY", idx)) + crossOriginOpenerPolicy, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__CROSS_ORIGIN_OPENER_POLICY", idx)) if ok { - result.CrossOriginOpenerPolicy = crossOriginOpenedPolicy + result.CrossOriginOpenerPolicy = crossOriginOpenerPolicy + isSet = true + } + + crossOriginResourcePolicy, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__CROSS_ORIGIN_RESOURCE_POLICY", idx)) + if ok { + result.CrossOriginResourcePolicy = crossOriginResourcePolicy + isSet = true + } + + crossOriginEmbedderPolicy, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__CROSS_ORIGIN_EMBEDDER_POLICY", idx)) + if ok { + result.CrossOriginEmbedderPolicy = crossOriginEmbedderPolicy isSet = true } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index a895c542..5cad8732 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1230,6 +1230,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CONTENT_SECURITY_POLICY", "script-src $NONCE") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__PERMISSIONS_POLICY", "fullscreen=(), geolocation=()") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_OPENER_POLICY", "same-origin") + os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_RESOURCE_POLICY", "same-site") + os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_EMBEDDER_POLICY", "require-corp") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CACHE_CONTROL", "private") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH", "path1") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH", "path2") @@ -1297,6 +1299,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CONTENT_SECURITY_POLICY") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__PERMISSIONS_POLICY") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_OPENER_POLICY") + os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_RESOURCE_POLICY") + os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_EMBEDDER_POLICY") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CACHE_CONTROL") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH") @@ -1417,6 +1421,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.Equal(t, "script-src $NONCE", bindings[2].Security.ContentSecurityPolicy) require.Equal(t, "fullscreen=(), geolocation=()", bindings[2].Security.PermissionsPolicy) require.Equal(t, "same-origin", bindings[2].Security.CrossOriginOpenerPolicy) + require.Equal(t, "same-site", bindings[2].Security.CrossOriginResourcePolicy) + require.Equal(t, "require-corp", bindings[2].Security.CrossOriginEmbedderPolicy) require.Equal(t, "private", bindings[2].Security.CacheControl) require.Equal(t, "favicon.ico", bindings[2].Branding.WebAdmin.FaviconPath) require.Equal(t, "logo.png", bindings[2].Branding.WebClient.LogoPath) diff --git a/internal/httpd/httpd.go b/internal/httpd/httpd.go index a50b47c7..8763c845 100644 --- a/internal/httpd/httpd.go +++ b/internal/httpd/httpd.go @@ -414,8 +414,12 @@ type SecurityConf struct { ContentSecurityPolicy string `json:"content_security_policy" mapstructure:"content_security_policy"` // PermissionsPolicy allows to set the Permissions-Policy header value. Default is "". PermissionsPolicy string `json:"permissions_policy" mapstructure:"permissions_policy"` - // CrossOriginOpenerPolicy allows to set the `Cross-Origin-Opener-Policy` header value. Default is "". + // CrossOriginOpenerPolicy allows to set the Cross-Origin-Opener-Policy header value. Default is "". CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" mapstructure:"cross_origin_opener_policy"` + // CrossOriginResourcePolicy allows to set the Cross-Origin-Opener-Policy header value. Default is "". + CrossOriginResourcePolicy string `json:"cross_origin_resource_policy" mapstructure:"cross_origin_resource_policy"` + // CrossOriginEmbedderPolicy allows to set the Cross-Origin-Embedder-Policy header value. Default is "". + CrossOriginEmbedderPolicy string `json:"cross_origin_embedder_policy" mapstructure:"cross_origin_embedder_policy"` // CacheControl allow to set the Cache-Control header value. CacheControl string `json:"cache_control" mapstructure:"cache_control"` proxyHeaders []string diff --git a/internal/httpd/internal_test.go b/internal/httpd/internal_test.go index a9179cc4..e73dffc1 100644 --- a/internal/httpd/internal_test.go +++ b/internal/httpd/internal_test.go @@ -3389,11 +3389,14 @@ func TestSecureMiddlewareIntegration(t *testing.T) { Value: "https", }, }, - STSSeconds: 31536000, - STSIncludeSubdomains: true, - STSPreload: true, - ContentTypeNosniff: true, - CacheControl: "private", + STSSeconds: 31536000, + STSIncludeSubdomains: true, + STSPreload: true, + ContentTypeNosniff: true, + CacheControl: "private", + CrossOriginOpenerPolicy: "same-origin", + CrossOriginResourcePolicy: "same-site", + CrossOriginEmbedderPolicy: "require-corp", }, }, enableWebAdmin: true, @@ -3448,6 +3451,9 @@ func TestSecureMiddlewareIntegration(t *testing.T) { assert.NotEmpty(t, r.Header.Get(forwardedHostHeader)) assert.Equal(t, "max-age=31536000; includeSubDomains; preload", rr.Header().Get("Strict-Transport-Security")) assert.Equal(t, "nosniff", rr.Header().Get("X-Content-Type-Options")) + assert.Equal(t, "require-corp", rr.Header().Get("Cross-Origin-Embedder-Policy")) + assert.Equal(t, "same-origin", rr.Header().Get("Cross-Origin-Opener-Policy")) + assert.Equal(t, "same-site", rr.Header().Get("Cross-Origin-Resource-Policy")) server.binding.Security.Enabled = false server.binding.Security.updateProxyHeaders() diff --git a/internal/httpd/server.go b/internal/httpd/server.go index 8219045f..a5446695 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -1244,17 +1244,19 @@ func (s *httpdServer) initializeRouter() { s.router.Use(middleware.Recoverer) if s.binding.Security.Enabled { secureMiddleware := secure.New(secure.Options{ - AllowedHosts: s.binding.Security.AllowedHosts, - AllowedHostsAreRegex: s.binding.Security.AllowedHostsAreRegex, - HostsProxyHeaders: s.binding.Security.HostsProxyHeaders, - SSLProxyHeaders: s.binding.Security.getHTTPSProxyHeaders(), - STSSeconds: s.binding.Security.STSSeconds, - STSIncludeSubdomains: s.binding.Security.STSIncludeSubdomains, - STSPreload: s.binding.Security.STSPreload, - ContentTypeNosniff: s.binding.Security.ContentTypeNosniff, - ContentSecurityPolicy: s.binding.Security.ContentSecurityPolicy, - PermissionsPolicy: s.binding.Security.PermissionsPolicy, - CrossOriginOpenerPolicy: s.binding.Security.CrossOriginOpenerPolicy, + AllowedHosts: s.binding.Security.AllowedHosts, + AllowedHostsAreRegex: s.binding.Security.AllowedHostsAreRegex, + HostsProxyHeaders: s.binding.Security.HostsProxyHeaders, + SSLProxyHeaders: s.binding.Security.getHTTPSProxyHeaders(), + STSSeconds: s.binding.Security.STSSeconds, + STSIncludeSubdomains: s.binding.Security.STSIncludeSubdomains, + STSPreload: s.binding.Security.STSPreload, + ContentTypeNosniff: s.binding.Security.ContentTypeNosniff, + ContentSecurityPolicy: s.binding.Security.ContentSecurityPolicy, + PermissionsPolicy: s.binding.Security.PermissionsPolicy, + CrossOriginOpenerPolicy: s.binding.Security.CrossOriginOpenerPolicy, + CrossOriginResourcePolicy: s.binding.Security.CrossOriginResourcePolicy, + CrossOriginEmbedderPolicy: s.binding.Security.CrossOriginEmbedderPolicy, }) secureMiddleware.SetBadHostHandler(http.HandlerFunc(s.badHostHandler)) if s.binding.Security.CacheControl == "private" { diff --git a/sftpgo.json b/sftpgo.json index 142255a6..591087eb 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -321,6 +321,8 @@ "content_security_policy": "", "permissions_policy": "", "cross_origin_opener_policy": "", + "cross_origin_resource_policy": "", + "cross_origin_embedder_policy": "", "cache_control": "" }, "branding": {