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 <[email protected]>
AuthorDate: Mon Mar 14 09:25:21 2022 +0800

    feat: add security header (#2341)
    
    Co-authored-by: 阿杰鲁 <[email protected]>
---
 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

Reply via email to