mirror of
https://github.com/owncloud/ocis
synced 2026-04-25 17:25:21 +02:00
fix: Strict-Transport-Security via proxy
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
Enhancement: Force Strict-Transport-Security
|
||||
|
||||
Added `PROXY_FORCE_STRICT_TRANSPORT_SECURITY` environment variable to force emission of `Strict-Transport-Security` header on all responses, including plain HTTP requests when TLS is terminated upstream. Useful when oCIS is deployed behind a proxy.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/11880
|
||||
@@ -385,6 +385,7 @@ When using the ocis IDP service instead of an external IDP:
|
||||
- Use the environment variable `OCIS_URL` to define how ocis can be accessed, mandatory use `https` as protocol for the URL.
|
||||
- If no reverse proxy is set up, the `PROXY_TLS` environment variable **must** be set to `true` because the embedded `libreConnect` shipped with the IDP service has a hard check if the connection is on TLS and uses the HTTPS protocol. If this mismatches, an error will be logged and no connection from the client can be established.
|
||||
- `PROXY_TLS` **can** be set to `false` if a reverse proxy is used and the https connection is terminated at the reverse proxy. When setting to `false`, the communication between the reverse proxy and ocis is not secured. If set to `true`, you must provide certificates.
|
||||
- `PROXY_FORCE_STRICT_TRANSPORT_SECURITY`: Set to `true` to force emission of the `Strict-Transport-Security` header on all responses, including plain HTTP requests. Required when `PROXY_TLS=false` (TLS terminated upstream) to ensure the header is emitted despite oCIS receiving plain HTTP from the reverse proxy.
|
||||
|
||||
## Metrics
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config,
|
||||
middleware.AccessLog(logger),
|
||||
middleware.ContextLogger(logger),
|
||||
middleware.HTTPSRedirect(cfg.Commons.OcisURL),
|
||||
middleware.Security(cspConfig),
|
||||
middleware.Security(cfg, cspConfig),
|
||||
router.Middleware(serviceSelector, cfg.PolicySelector, cfg.Policies, logger),
|
||||
middleware.Authentication(
|
||||
authenticators,
|
||||
|
||||
@@ -2,10 +2,11 @@ package config
|
||||
|
||||
// HTTP defines the available http configuration.
|
||||
type HTTP struct {
|
||||
Addr string `yaml:"addr" env:"PROXY_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"pre5.0"`
|
||||
Root string `yaml:"root" env:"PROXY_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"pre5.0"`
|
||||
Namespace string `yaml:"-"`
|
||||
TLSCert string `yaml:"tls_cert" env:"PROXY_TRANSPORT_TLS_CERT" desc:"Path/File name of the TLS server certificate (in PEM format) for the external http services. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH/proxy." introductionVersion:"pre5.0"`
|
||||
TLSKey string `yaml:"tls_key" env:"PROXY_TRANSPORT_TLS_KEY" desc:"Path/File name for the TLS certificate key (in PEM format) for the server certificate to use for the external http services. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH/proxy." introductionVersion:"pre5.0"`
|
||||
TLS bool `yaml:"tls" env:"PROXY_TLS" desc:"Enable/Disable HTTPS for external HTTP services. Must be set to 'true' if the built-in IDP service an no reverse proxy is used. See the text description for details." introductionVersion:"pre5.0"`
|
||||
Addr string `yaml:"addr" env:"PROXY_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"pre5.0"`
|
||||
Root string `yaml:"root" env:"PROXY_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"pre5.0"`
|
||||
Namespace string `yaml:"-"`
|
||||
TLSCert string `yaml:"tls_cert" env:"PROXY_TRANSPORT_TLS_CERT" desc:"Path/File name of the TLS server certificate (in PEM format) for the external http services. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH/proxy." introductionVersion:"pre5.0"`
|
||||
TLSKey string `yaml:"tls_key" env:"PROXY_TRANSPORT_TLS_KEY" desc:"Path/File name for the TLS certificate key (in PEM format) for the server certificate to use for the external http services. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH/proxy." introductionVersion:"pre5.0"`
|
||||
TLS bool `yaml:"tls" env:"PROXY_TLS" desc:"Enable/Disable HTTPS for external HTTP services. Must be set to 'true' if the built-in IDP service an no reverse proxy is used. See the text description for details." introductionVersion:"pre5.0"`
|
||||
ForceStrictTransportSecurity bool `yaml:"force_strict_transport_security" env:"PROXY_FORCE_STRICT_TRANSPORT_SECURITY" desc:"Force emission of the Strict-Transport-Security header on all responses, including plain HTTP requests. See also: PROXY_TLS" introductionVersion:"Curie"`
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func loadCSPYaml(proxyCfg *config.Config) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Security is a middleware to apply security relevant http headers like CSP.
|
||||
func Security(cspConfig *config.CSP) func(h http.Handler) http.Handler {
|
||||
func Security(cfg *config.Config, cspConfig *config.CSP) func(h http.Handler) http.Handler {
|
||||
cspBuilder := cspbuilder.Builder{
|
||||
Directives: cspConfig.Directives,
|
||||
}
|
||||
@@ -61,6 +61,7 @@ func Security(cspConfig *config.CSP) func(h http.Handler) http.Handler {
|
||||
FrameDeny: true,
|
||||
ReferrerPolicy: "no-referrer",
|
||||
STSSeconds: 315360000,
|
||||
ForceSTSHeader: cfg.HTTP.ForceStrictTransportSecurity,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
PermittedCrossDomainPolicies: "none",
|
||||
|
||||
@@ -42,7 +42,8 @@ func TestStrictTransportSecurity(t *testing.T) {
|
||||
"default-src": {"'none'"},
|
||||
},
|
||||
}
|
||||
securityMiddleware := Security(cspConfig)
|
||||
cfg := &config.Config{HTTP: config.HTTP{ForceStrictTransportSecurity: false}}
|
||||
securityMiddleware := Security(cfg, cspConfig)
|
||||
|
||||
// Test HTTPS request, url not important, only headers will be checked
|
||||
req, err := http.NewRequest("GET", "https://example.com", nil)
|
||||
@@ -60,3 +61,26 @@ func TestStrictTransportSecurity(t *testing.T) {
|
||||
expected := "max-age=315360000; includeSubDomains; preload"
|
||||
assert.Equal(t, hstsHeader, expected, "HSTS header missing includeSubDomains directive - subdomains not protected")
|
||||
}
|
||||
|
||||
func TestStrictTransportSecurity_ForceOnHTTP(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
cspConfig := &config.CSP{
|
||||
Directives: map[string][]string{
|
||||
"default-src": {"'none'"},
|
||||
},
|
||||
}
|
||||
cfg := &config.Config{HTTP: config.HTTP{ForceStrictTransportSecurity: true}}
|
||||
securityMiddleware := Security(cfg, cspConfig)
|
||||
|
||||
// Plain HTTP request (no TLS); should still emit Strict-Transport-Security when forced.
|
||||
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
securityMiddleware(handler).ServeHTTP(rr, req)
|
||||
|
||||
stsHeader := rr.Header().Get("Strict-Transport-Security")
|
||||
expected := "max-age=315360000; includeSubDomains; preload"
|
||||
assert.Equal(t, stsHeader, expected)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user