This is an automated email from the ASF dual-hosted git repository. mark4z pushed a commit to branch 0.4.0 in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git
commit 70bdd3eeb4029a633ea126420a2bd750681a8ecd Author: baerwang <[email protected]> AuthorDate: Sun Oct 17 19:52:18 2021 +0800 add:csrf Former-commit-id: 154ad17d311c624e4c2c6a4bee4c16337197545a --- pkg/common/constant/key.go | 1 + pkg/filter/csrf/csrf.go | 141 +++++++++++++++++++++++++ pkg/pluginregistry/registry.go | 1 + samples/dubbogo/simple/csrf/pixiu/conf.yaml | 78 ++++++++++++++ samples/dubbogo/simple/csrf/server/server.go | 31 ++++++ samples/dubbogo/simple/csrf/test/pixiu_test.go | 58 ++++++++++ 6 files changed, 310 insertions(+) diff --git a/pkg/common/constant/key.go b/pkg/common/constant/key.go index d51563d..f3335e7 100644 --- a/pkg/common/constant/key.go +++ b/pkg/common/constant/key.go @@ -35,6 +35,7 @@ const ( HTTPTimeoutFilter = "dgp.filter.http.timeout" TracingFilter = "dgp.filters.tracing" HTTPCorsFilter = "dgp.filter.http.cors" + HTTPCsrfFilter = "dgp.filter.http.csrf" HTTPProxyRewriteFilter = "dgp.filter.http.proxyrewrite" ) diff --git a/pkg/filter/csrf/csrf.go b/pkg/filter/csrf/csrf.go new file mode 100644 index 0000000..ba85f08 --- /dev/null +++ b/pkg/filter/csrf/csrf.go @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package csrf + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "github.com/apache/dubbo-go-pixiu/pkg/common/constant" + "github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter" + "github.com/apache/dubbo-go-pixiu/pkg/context/http" + http2 "net/http" +) + +const ( + // Kind is the kind of Fallback. + Kind = constant.HTTPCsrfFilter +) + +const ( + csrfSecret = "csrfSecret" + csrfSalt = "csrfSalt" +) + +func init() { + filter.RegisterHttpFilter(&Plugin{}) +} + +type ( + // Plugin is http filter plugin. + Plugin struct { + } + + // Filter is http filter instance + Filter struct { + cfg *Config + } + + // Config describe the config of Filter + Config struct { + Key string `yaml:"key" json:"key" mapstructure:"key"` // get request key + Secret string `yaml:"secret" json:"secret" mapstructure:"secret"` // private key + ErrorMsg string `yaml:"error_msg" json:"error_msg" mapstructure:"error_msg"` // hint error info + IgnoreMethods []string `yaml:"ignore_methods" json:"ignore_methods" mapstructure:"ignore_methods"` // ignore request method + } +) + +func (p *Plugin) Kind() string { + return Kind +} + +func (p *Plugin) CreateFilter() (filter.HttpFilter, error) { + return &Filter{cfg: &Config{}}, nil +} + +func (f *Filter) PrepareFilterChain(ctx *http.HttpContext) error { + ctx.AppendFilterFunc(f.Handle) + return nil +} + +func (f *Filter) Handle(ctx *http.HttpContext) { + f.handleCsrf(ctx) +} + +func (f *Filter) handleCsrf(ctx *http.HttpContext) { + ctx.Request.Header.Set(csrfSecret, f.cfg.Secret) + + if inMethod(f.cfg.IgnoreMethods, ctx.Request.Method) { + ctx.Next() + return + } + + salt := ctx.Request.Header.Get(csrfSalt) + + if salt == "" { + bt, _ := json.Marshal(http.ErrResponse{Message: f.cfg.ErrorMsg}) + ctx.WriteJSONWithStatus(http2.StatusForbidden, bt) + ctx.Abort() + return + } + + token := tokenize(f.cfg.Secret, salt) + + if token != tokenGetter(ctx, f.cfg.Key) { + bt, _ := json.Marshal(http.ErrResponse{Message: f.cfg.ErrorMsg}) + ctx.WriteJSONWithStatus(http2.StatusForbidden, bt) + ctx.Abort() + return + } + + ctx.Next() + +} + +func tokenGetter(ctx *http.HttpContext, key string) string { + req := ctx.Request + if t := req.Form.Get(key); t != "" { + return t + } else if t := req.URL.Query().Get(key); t != "" { + return t + } else if t := req.Header.Get(key); t != "" { + return t + } + return "" +} + +func inMethod(methods []string, method string) bool { + for _, v := range methods { + if v == method { + return true + } + } + return false +} + +func tokenize(secret, salt string) string { + return base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%s-%s", salt, secret))) +} + +func (f *Filter) Apply() error { + return nil +} + +func (f *Filter) Config() interface{} { + return f.cfg +} diff --git a/pkg/pluginregistry/registry.go b/pkg/pluginregistry/registry.go index 7c97bb2..027619d 100644 --- a/pkg/pluginregistry/registry.go +++ b/pkg/pluginregistry/registry.go @@ -24,6 +24,7 @@ import ( _ "github.com/apache/dubbo-go-pixiu/pkg/filter/accesslog" _ "github.com/apache/dubbo-go-pixiu/pkg/filter/authority" _ "github.com/apache/dubbo-go-pixiu/pkg/filter/cors" + _ "github.com/apache/dubbo-go-pixiu/pkg/filter/csrf" _ "github.com/apache/dubbo-go-pixiu/pkg/filter/header" _ "github.com/apache/dubbo-go-pixiu/pkg/filter/host" _ "github.com/apache/dubbo-go-pixiu/pkg/filter/http/apiconfig" diff --git a/samples/dubbogo/simple/csrf/pixiu/conf.yaml b/samples/dubbogo/simple/csrf/pixiu/conf.yaml new file mode 100644 index 0000000..093f9ce --- /dev/null +++ b/samples/dubbogo/simple/csrf/pixiu/conf.yaml @@ -0,0 +1,78 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +static_resources: + listeners: + - name: "net/http" + address: + socket_address: + protocol_type: "HTTP" + address: "0.0.0.0" + port: 8888 + filter_chains: + - filter_chain_match: + domains: + - api.dubbo.com + - api.pixiu.com + filters: + - name: dgp.filter.httpconnectionmanager + config: + route_config: + routes: + - match: + prefix: "/user" + route: + cluster: "user" + cluster_not_found_response_code: 505 + http_filters: + - name: dgp.filter.http.httpproxy + config: + - name: dgp.filter.http.cors + config: + allow_origin: + - api.dubbo.com + allow_methods: "" + allow_headers: "" + expose_headers: "" + max_age: "" + allow_credentials: false + - name: dgp.filter.http.csrf + config: + key: pixiu + secret: pixiu888 + ignore_methods: + error_msg: "CSRF token mismatch" + - name: dgp.filter.http.response + config: + config: + idle_timeout: 5s + read_timeout: 5s + write_timeout: 5s + clusters: + - name: "user" + lb_policy: "lb" + endpoints: + - id: 1 + socket_address: + address: 127.0.0.1 + port: 1314 + shutdown_config: + timeout: "60s" + step_timeout: "10s" + reject_policy: "immediacy" \ No newline at end of file diff --git a/samples/dubbogo/simple/csrf/server/server.go b/samples/dubbogo/simple/csrf/server/server.go new file mode 100644 index 0000000..6eccca7 --- /dev/null +++ b/samples/dubbogo/simple/csrf/server/server.go @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "log" + "net/http" +) + +func main() { + http.HandleFunc("/user/", func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"message":"success","status":200}`)) + }) + log.Println("Starting sample server ...") + log.Fatal(http.ListenAndServe(":1314", nil)) +} diff --git a/samples/dubbogo/simple/csrf/test/pixiu_test.go b/samples/dubbogo/simple/csrf/test/pixiu_test.go new file mode 100644 index 0000000..2fed5cf --- /dev/null +++ b/samples/dubbogo/simple/csrf/test/pixiu_test.go @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package csrf + +import ( + "github.com/stretchr/testify/assert" + "io/ioutil" + "net/http" + "strings" + "testing" + "time" +) + +func TestCsrfHeader(t *testing.T) { + urlStr := "http://localhost:8888/user/" + client := &http.Client{Timeout: 5 * time.Second} + req, err := http.NewRequest("GET", urlStr, nil) + assert.NoError(t, err) + req.Header.Set("csrfSalt", "pixiu") + req.Header.Set("pixiu", "cGl4aXUtcGl4aXU4ODg=") + resp, err := client.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, resp) + s, _ := ioutil.ReadAll(resp.Body) + t.Log(string(s)) + assert.True(t, strings.Contains(string(s), "success")) +} + +func TestCsrfQuery(t *testing.T) { + urlStr := "http://localhost:8888/user?pixiu=cGl4aXUtcGl4aXU4ODg=" + client := &http.Client{Timeout: 5 * time.Second} + req, err := http.NewRequest("GET", urlStr, nil) + assert.NoError(t, err) + req.Header.Set("csrfSalt", "pixiu") + resp, err := client.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.NotNil(t, resp) + s, _ := ioutil.ReadAll(resp.Body) + t.Log(string(s)) + assert.True(t, strings.Contains(string(s), "success")) +}
