You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by bz...@apache.org on 2022/03/14 01:25:29 UTC

[apisix-dashboard] branch master updated: feat: add security header (#2341)

This is an automated email from the ASF dual-hosted git repository.

bzp2010 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new edca223  feat: add security header (#2341)
edca223 is described below

commit edca223f0169cbba24d62a56f2d39b01c97756b9
Author: Yu.Bozhong <y....@foxmail.com>
AuthorDate: Mon Mar 14 09:25:21 2022 +0800

    feat: add security header (#2341)
    
    Co-authored-by: 阿杰鲁 <im...@gmail.com>
---
 api/conf/conf.yaml          |  8 ++++++++
 api/internal/conf/conf.go   | 35 +++++++++++++++++++++++++++++++++++
 api/internal/filter/cors.go | 33 ++++++++++++++++++++++++++++-----
 api/test/shell/cli_test.sh  | 26 ++++++++++++++++++++++++++
 4 files changed, 97 insertions(+), 5 deletions(-)

diff --git a/api/conf/conf.yaml b/api/conf/conf.yaml
index 3090194..84e1f3a 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 b0a7328..5087edf 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 b33c62b..28ca331 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 be1d3a8..d41f270 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