diff --git a/api/conf/conf.yaml b/api/conf/conf.yaml index 30901946a5..84e1f3aba7 100644 --- a/api/conf/conf.yaml +++ b/api/conf/conf.yaml @@ -60,6 +60,14 @@ conf: # such as absolute path on Windows: winfile:///C:\access.log # log example: 2020-12-09T16:38:09.039+0800 INFO filter/logging.go:46 /apisix/admin/routes/r1 {"status": 401, "host": "127.0.0.1:9000", "query": "asdfsafd=adf&a=a", "requestId": "3d50ecb8-758c-46d1-af5b-cd9d1c820156", "latency": 0, "remoteIP": "127.0.0.1", "method": "PUT", "errs": []} max_cpu: 0 # supports tweaking with the number of OS threads are going to be used for parallelism. Default value: 0 [will use max number of available cpu cores considering hyperthreading (if any)]. If the value is negative, is will not touch the existing parallelism profile. + # security: + # access_control_allow_origin: "http://httpbin.org" + # access_control_allow_credentials: true # support using custom cors configration + # access_control_allow_headers: "Authorization" + # access_control-allow_methods: "*" + # x_frame_options: "deny" + # content_security_policy: ""default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"" + authentication: secret: diff --git a/api/internal/conf/conf.go b/api/internal/conf/conf.go index b0a7328e75..5087edf77d 100644 --- a/api/internal/conf/conf.go +++ b/api/internal/conf/conf.go @@ -61,6 +61,7 @@ var ( ImportSizeLimit = 10 * 1024 * 1024 AllowList []string Plugins = map[string]bool{} + SecurityConf Security ) type MTLS struct { @@ -110,6 +111,7 @@ type Conf struct { Log Log AllowList []string `mapstructure:"allow_list"` MaxCpu int `mapstructure:"max_cpu"` + Security Security } type User struct { @@ -129,6 +131,15 @@ type Config struct { Plugins []string } +type Security struct { + AllowCredentials string `mapstructure:"access_control_allow_credentials"` + AllowOrigin string `mapstructure:"access_control_allow_origin"` + AllowMethods string `mapstructure:"access_control-allow_methods"` + AllowHeaders string `mapstructure:"access_control_allow_headers"` + XFrameOptions string `mapstructure:"x_frame_options"` + ContentSecurityPolicy string `mapstructure:"content_security_policy"` +} + // TODO: we should no longer use init() function after remove all handler's integration tests // ENV=test is for integration tests only, other ENV should call "InitConf" explicitly func init() { @@ -246,6 +257,9 @@ func setupConfig() { // set plugin initPlugins(config.Plugins) + + // security configuration + initSecurity(config.Conf.Security) } func setupEnv() { @@ -316,3 +330,24 @@ func initParallelism(choiceCores int) { } runtime.GOMAXPROCS(choiceCores) } + +// initialize security settings +func initSecurity(conf Security) { + var se Security + // if conf == se, then conf is empty, we should use default value + if conf != se { + SecurityConf = conf + if conf.ContentSecurityPolicy == "" { + SecurityConf.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" + } + if conf.XFrameOptions == "" { + SecurityConf.XFrameOptions = "deny" + } + return + } + + SecurityConf = Security{ + XFrameOptions: "deny", + ContentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'", + } +} diff --git a/api/internal/filter/cors.go b/api/internal/filter/cors.go index b33c62b94f..28ca331625 100644 --- a/api/internal/filter/cors.go +++ b/api/internal/filter/cors.go @@ -16,14 +16,37 @@ */ package filter -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" + + "github.com/apisix/manager-api/internal/conf" +) func CORS() gin.HandlerFunc { return func(c *gin.Context) { - c.Writer.Header().Set("Access-Control-Allow-Origin", "*") - c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") - c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization") - c.Writer.Header().Set("Access-Control-Allow-Methods", "*") + if conf.SecurityConf.AllowOrigin != "" { + c.Writer.Header().Set("Access-Control-Allow-Origin", conf.SecurityConf.AllowOrigin) + } + + if conf.SecurityConf.AllowHeaders != "" { + c.Writer.Header().Set("Access-Control-Allow-Headers", conf.SecurityConf.AllowHeaders) + } + + if conf.SecurityConf.AllowMethods != "" { + c.Writer.Header().Set("Access-Control-Allow-Methods", conf.SecurityConf.AllowMethods) + } + + if conf.SecurityConf.AllowCredentials != "" { + c.Writer.Header().Set("Access-Control-Allow-Credentials", conf.SecurityConf.AllowCredentials) + } + + if conf.SecurityConf.XFrameOptions != "" { + c.Writer.Header().Set("X-Frame-Options", conf.SecurityConf.XFrameOptions) + } + + if conf.SecurityConf.ContentSecurityPolicy != "" { + c.Writer.Header().Set("Content-Security-Policy", conf.SecurityConf.ContentSecurityPolicy) + } if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return diff --git a/api/test/shell/cli_test.sh b/api/test/shell/cli_test.sh index be1d3a834c..d41f270570 100755 --- a/api/test/shell/cli_test.sh +++ b/api/test/shell/cli_test.sh @@ -454,6 +454,32 @@ stop_dashboard() { recover_service_file } +#15 +@test "Check Security configuration" { + recover_conf + + start_dashboard 3 + + # check response header without custom header + run curl -i http://127.0.0.1:9000 + + [ $(echo "$output" | grep -c "X-Frame-Options: deny") -eq '1' ] + + stop_dashboard 6 + + sed -i 's@# security:@security:@' ${CONF_FILE} + sed -i 's@# x_frame_options: "deny"@ x_frame_options: "test"@' ${CONF_FILE} + + start_dashboard 3 + + # check response header with custom header + run curl -i http://127.0.0.1:9000 + +[ $(echo "$output" | grep -c "X-Frame-Options: test") -eq '1' ] + + stop_dashboard 6 +} + #post @test "Clean test environment" { # kill etcd