diff --git a/cmd/common-main.go b/cmd/common-main.go index 75f8d1487..71cf8d0e0 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -178,6 +178,23 @@ func minioConfigToConsoleFeatures() { os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region) os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", "")) + // This section sets Browser (console) stored config + if valueSCP := globalBrowserConfig.GetCSPolicy(); valueSCP != "" { + os.Setenv("CONSOLE_SECURE_CONTENT_SECURITY_POLICY", valueSCP) + } + + if hstsSeconds := globalBrowserConfig.GetHSTSSeconds(); hstsSeconds > 0 { + isubdom := globalBrowserConfig.IsHSTSIncludeSubdomains() + isprel := globalBrowserConfig.IsHSTSPreload() + os.Setenv("CONSOLE_SECURE_STS_SECONDS", strconv.Itoa(hstsSeconds)) + os.Setenv("CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS", isubdom) + os.Setenv("CONSOLE_SECURE_STS_PRELOAD", isprel) + } + + if valueRefer := globalBrowserConfig.GetReferPolicy(); valueRefer != "" { + os.Setenv("CONSOLE_SECURE_REFERRER_POLICY", valueRefer) + } + globalSubnetConfig.ApplyEnv() } diff --git a/cmd/config-current.go b/cmd/config-current.go index e8f3a303f..9dc033a69 100644 --- a/cmd/config-current.go +++ b/cmd/config-current.go @@ -24,6 +24,8 @@ import ( "strings" "sync" + "github.com/minio/minio/internal/config/browser" + "github.com/minio/madmin-go/v3" "github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config/api" @@ -74,6 +76,7 @@ func initHelp() { config.DriveSubSys: drive.DefaultKVS, config.CacheSubSys: cache.DefaultKVS, config.BatchSubSys: batch.DefaultKVS, + config.BrowserSubSys: browser.DefaultKVS, } for k, v := range notify.DefaultNotificationKVS { kvs[k] = v @@ -226,6 +229,11 @@ func initHelp() { Description: "enable cache plugin on MinIO for GET/HEAD requests", Optional: true, }, + config.HelpKV{ + Key: config.BrowserSubSys, + Description: "manage Browser HTTP specific features, such as Security headers, etc.", + Optional: true, + }, } if globalIsErasure { @@ -273,6 +281,7 @@ func initHelp() { config.CallhomeSubSys: callhome.HelpCallhome, config.DriveSubSys: drive.HelpDrive, config.CacheSubSys: cache.Help, + config.BrowserSubSys: browser.Help, } config.RegisterHelpSubSys(helpMap) @@ -407,6 +416,10 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o return err } } + case config.BrowserSubSys: + if _, err := browser.LookupConfig(s[config.BrowserSubSys][config.Default]); err != nil { + return err + } default: if config.LoggerSubSystems.Contains(subSys) { if err := logger.ValidateSubSysConfig(ctx, s, subSys); err != nil { @@ -689,6 +702,12 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf } else { globalCacheConfig.Update(cacheCfg) } + case config.BrowserSubSys: + browserCfg, err := browser.LookupConfig(s[config.BrowserSubSys][config.Default]) + if err != nil { + return fmt.Errorf("Unable to apply browser config: %w", err) + } + globalBrowserConfig.Update(browserCfg) } globalServerConfigMu.Lock() defer globalServerConfigMu.Unlock() diff --git a/cmd/globals.go b/cmd/globals.go index b4ab43098..8f2b16617 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -33,6 +33,7 @@ import ( "github.com/minio/minio/internal/bpool" "github.com/minio/minio/internal/bucket/bandwidth" "github.com/minio/minio/internal/config" + "github.com/minio/minio/internal/config/browser" "github.com/minio/minio/internal/handlers" "github.com/minio/minio/internal/kms" "go.uber.org/atomic" @@ -191,6 +192,9 @@ var ( // Disable redirect, default is enabled. globalBrowserRedirect bool + // globalBrowserConfig Browser user configurable settings + globalBrowserConfig browser.Config + // This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false. globalInplaceUpdateDisabled = false diff --git a/internal/config/browser/browser.go b/internal/config/browser/browser.go new file mode 100644 index 000000000..a67451a92 --- /dev/null +++ b/internal/config/browser/browser.go @@ -0,0 +1,164 @@ +// Copyright (c) 2015-2023 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package browser + +import ( + "fmt" + "strconv" + "sync" + + "github.com/minio/minio/internal/config" + "github.com/minio/pkg/v2/env" +) + +// Browser sub-system constants +const ( + // browserCSPPolicy setting name for Content-Security-Policy response header value + browserCSPPolicy = "csp_policy" + // browserHSTSSeconds setting name for Strict-Transport-Security response header, amount of seconds for 'max-age' + browserHSTSSeconds = "hsts_seconds" + // browserHSTSIncludeSubdomains setting name for Strict-Transport-Security response header 'includeSubDomains' flag (true or false) + browserHSTSIncludeSubdomains = "hsts_include_subdomains" + // browserHSTSPreload setting name for Strict-Transport-Security response header 'preload' flag (true or false) + browserHSTSPreload = "hsts_preload" + // browserReferrerPolicy setting name for Referrer-Policy response header + browserReferrerPolicy = "referrer_policy" + + EnvBrowserCSPPolicy = "MINIO_BROWSER_CONTENT_SECURITY_POLICY" + EnvBrowserHSTSSeconds = "MINIO_BROWSER_HSTS_SECONDS" + EnvBrowserHSTSIncludeSubdomains = "MINIO_BROWSER_HSTS_INCLUDE_SUB_DOMAINS" + EnvBrowserHSTSPreload = "MINIO_BROWSER_HSTS_PRELOAD" + EnvBrowserReferrerPolicy = "MINIO_BROWSER_REFERRER_POLICY" +) + +// DefaultKVS - default storage class config +var ( + DefaultKVS = config.KVS{ + config.KV{ + Key: browserCSPPolicy, + Value: "default-src 'self' 'unsafe-eval' 'unsafe-inline';", + }, + config.KV{ + Key: browserHSTSSeconds, + Value: "0", + }, + config.KV{ + Key: browserHSTSIncludeSubdomains, + Value: config.EnableOff, + }, + config.KV{ + Key: browserHSTSPreload, + Value: config.EnableOff, + }, + config.KV{ + Key: browserReferrerPolicy, + Value: "strict-origin-when-cross-origin", + }, + } +) + +// configLock is a global lock for browser config +var configLock sync.RWMutex + +// Config storage class configuration +type Config struct { + CSPPolicy string `json:"csp_policy"` + HSTSSeconds int `json:"hsts_seconds"` + HSTSIncludeSubdomains bool `json:"hsts_include_subdomains"` + HSTSPreload bool `json:"hsts_preload"` + ReferrerPolicy string `json:"referrer_policy"` +} + +// Update Updates browser with new config +func (browseCfg *Config) Update(newCfg Config) { + configLock.Lock() + defer configLock.Unlock() + browseCfg.CSPPolicy = newCfg.CSPPolicy + browseCfg.HSTSSeconds = newCfg.HSTSSeconds + browseCfg.HSTSIncludeSubdomains = newCfg.HSTSIncludeSubdomains + browseCfg.HSTSPreload = newCfg.HSTSPreload + browseCfg.ReferrerPolicy = newCfg.ReferrerPolicy +} + +// LookupConfig - lookup api config and override with valid environment settings if any. +func LookupConfig(kvs config.KVS) (cfg Config, err error) { + cspPolicy := env.Get(EnvBrowserCSPPolicy, kvs.GetWithDefault(browserCSPPolicy, DefaultKVS)) + hstsSeconds, err := strconv.Atoi(env.Get(EnvBrowserHSTSSeconds, kvs.GetWithDefault(browserHSTSSeconds, DefaultKVS))) + if err != nil { + return cfg, err + } + + hstsIncludeSubdomains := env.Get(EnvBrowserHSTSIncludeSubdomains, kvs.GetWithDefault(browserHSTSIncludeSubdomains, DefaultKVS)) == config.EnableOn + hstsPreload := env.Get(EnvBrowserHSTSPreload, kvs.Get(browserHSTSPreload)) == config.EnableOn + + referrerPolicy := env.Get(EnvBrowserReferrerPolicy, kvs.GetWithDefault(browserReferrerPolicy, DefaultKVS)) + switch referrerPolicy { + case "no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin", "same-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url": + cfg.ReferrerPolicy = referrerPolicy + default: + return cfg, fmt.Errorf("invalid value %v for %s", referrerPolicy, browserReferrerPolicy) + } + + cfg.CSPPolicy = cspPolicy + cfg.HSTSSeconds = hstsSeconds + cfg.HSTSIncludeSubdomains = hstsIncludeSubdomains + cfg.HSTSPreload = hstsPreload + + return cfg, nil +} + +// GetCSPolicy - Get the Content security Policy +func (browseCfg *Config) GetCSPolicy() string { + configLock.RLock() + defer configLock.RUnlock() + return browseCfg.CSPPolicy +} + +// GetHSTSSeconds - Get the Content security Policy +func (browseCfg *Config) GetHSTSSeconds() int { + configLock.RLock() + defer configLock.RUnlock() + return browseCfg.HSTSSeconds +} + +// IsHSTSIncludeSubdomains - is HSTS 'includeSubdomains' directive enabled +func (browseCfg *Config) IsHSTSIncludeSubdomains() string { + configLock.RLock() + defer configLock.RUnlock() + if browseCfg.HSTSSeconds > 0 && browseCfg.HSTSIncludeSubdomains { + return config.EnableOn + } + return config.EnableOff +} + +// IsHSTSPreload - is HSTS 'preload' directive enabled +func (browseCfg *Config) IsHSTSPreload() string { + configLock.RLock() + defer configLock.RUnlock() + if browseCfg.HSTSSeconds > 0 && browseCfg.HSTSPreload { + return config.EnableOn + } + return config.EnableOff +} + +// GetReferPolicy - Get the ReferPolicy +func (browseCfg *Config) GetReferPolicy() string { + configLock.RLock() + defer configLock.RUnlock() + return browseCfg.ReferrerPolicy +} diff --git a/internal/config/browser/help.go b/internal/config/browser/help.go new file mode 100644 index 000000000..8f8483e20 --- /dev/null +++ b/internal/config/browser/help.go @@ -0,0 +1,60 @@ +// Copyright (c) 2015-2023 MinIO, Inc. +// +// # This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package browser + +import "github.com/minio/minio/internal/config" + +// Help template for browser feature. +var ( + defaultHelpPostfix = func(key string) string { + return config.DefaultHelpPostfix(DefaultKVS, key) + } + + Help = config.HelpKVS{ + config.HelpKV{ + Key: browserCSPPolicy, + Description: `set Content-Security-Policy response header value` + defaultHelpPostfix(browserCSPPolicy), + Optional: true, + Type: "string", + }, + config.HelpKV{ + Key: browserHSTSSeconds, + Description: `set Strict-Transport-Security 'max-age' amount of seconds value` + defaultHelpPostfix(browserHSTSSeconds), + Optional: true, + Type: "number", + }, + config.HelpKV{ + Key: browserHSTSIncludeSubdomains, + Description: `turn 'on' to set Strict-Transport-Security 'includeSubDomains' directive` + defaultHelpPostfix(browserHSTSIncludeSubdomains), + Optional: true, + Type: "boolean", + }, + config.HelpKV{ + Key: browserHSTSPreload, + Description: `turn 'on' to set Strict-Transport-Security 'preload' directive` + defaultHelpPostfix(browserHSTSPreload), + Optional: true, + Type: "boolean", + }, + config.HelpKV{ + Key: browserReferrerPolicy, + Description: `set Referrer-Policy response header value` + defaultHelpPostfix(browserReferrerPolicy), + Optional: true, + Type: "string", + }, + } +) diff --git a/internal/config/config.go b/internal/config/config.go index 63821894f..d329e0014 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -119,6 +119,7 @@ const ( CallhomeSubSys = madmin.CallhomeSubSys DriveSubSys = madmin.DriveSubSys BatchSubSys = madmin.BatchSubSys + BrowserSubSys = madmin.BrowserSubSys // Add new constants here (similar to above) if you add new fields to config. ) @@ -188,6 +189,7 @@ var SubSystemsDynamic = set.CreateStringSet( StorageClassSubSys, CacheSubSys, BatchSubSys, + BrowserSubSys, ) // SubSystemsSingleTargets - subsystems which only support single target. @@ -209,8 +211,8 @@ var SubSystemsSingleTargets = set.CreateStringSet( SubnetSubSys, CallhomeSubSys, DriveSubSys, - CacheSubSys, BatchSubSys, + BrowserSubSys, ) // Constant separators diff --git a/internal/config/constants.go b/internal/config/constants.go index 52406586d..0dc0f8343 100644 --- a/internal/config/constants.go +++ b/internal/config/constants.go @@ -82,6 +82,7 @@ const ( EnvWorm = "MINIO_WORM" // legacy EnvRegion = "MINIO_REGION" // legacy EnvRegionName = "MINIO_REGION_NAME" // legacy + ) // Expiration Token durations