Browse Source

httpd: add cross origin resource and embedder policy headers

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 8 months ago
parent
commit
48258f6e67

+ 31 - 17
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
 	}
 

+ 6 - 0
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)

+ 5 - 1
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

+ 11 - 5
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()

+ 13 - 11
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" {

+ 2 - 0
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": {