You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2021/12/07 10:27:22 UTC
[GitHub] [apisix] Baoyuantop opened a new pull request #5727: feat: add CSRF plugin
Baoyuantop opened a new pull request #5727:
URL: https://github.com/apache/apisix/pull/5727
### What this PR does / why we need it:
Add CSRF plugin for APISIX
Discuss in https://lists.apache.org/thread/ddj53svylg9s541vf2gpysxwgyrhwlzx
### Pre-submission checklist:
<!--
Please follow the requirements:
1. Use Draft if the PR is not ready to be reviewed
2. Test is required for the feat/fix PR, unless you have a good reason
3. Doc is required for the feat PR
4. Use a new commit to resolve review instead of `push -f`
5. Use "request review" to notify the reviewer once you have resolved the review
-->
* [x] Did you explain what problem does this PR solve? Or what new features have been added?
* [ ] Have you added corresponding test cases?
* [ ] Have you modified the corresponding document?
* [ ] Is this PR backward compatible? **If it is not backward compatible, please discuss on the [mailing list](https://github.com/apache/apisix/tree/master#community) first**
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] leslie-tsang commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r770335616
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,169 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local request_method = ngx.var.request_method
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local ck = require "resty.cookie"
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
Review comment:
Got it. Thanks for reply.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r774270754
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
Review comment:
Better to descript the time unit
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
Review comment:
Please check if _token is nil
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
Review comment:
Why skip GET?
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in request header"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
Review comment:
Why only set cookie for GET?
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
Review comment:
Let's move the name to the schema.
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
Review comment:
```suggestion
core.log.error("no random in token")
```
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
Review comment:
```suggestion
local ngx_time = ngx.time
```
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
Review comment:
Ditto
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in request header"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
Review comment:
We can use ctx.var.cookie_xxx
##########
File path: conf/config-default.yaml
##########
@@ -330,6 +330,7 @@ plugins: # plugin list (sorted by priority)
- serverless-pre-function # priority: 10000
- batch-requests # priority: 4010
- cors # priority: 4000
+ - csrf # priority: 3500
Review comment:
IMHO, putting it under `referer-restriction` would be better?
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in request header"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local method = ctx.var.request_method
Review comment:
Let's use `core.request.get_method`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] tzssangglass commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r775959656
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is nil")
+ return false
+ end
+
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in request header"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local csrf_token = gen_csrf_token(conf)
+ core.response.add_header("Set-Cookie", {conf.name .. "=" .. csrf_token
+ .. ";path=/;Expires="
+ .. cookie_time(ngx_time() + conf.expires)})
Review comment:
```suggestion
local cookie = conf.name .. "=" .. csrf_token .. ";path=/;Expires="
.. cookie_time(ngx_time() + conf.expires)
core.response.add_header("Set-Cookie", cookie)
```
is better?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785709424
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Sure. Sounds good to me. Thanks
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] juzhiyuan commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
juzhiyuan commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r776939023
##########
File path: docs/zh/latest/plugins/csrf.md
##########
@@ -65,7 +65,13 @@ curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335
这条路由已经开启保护,当你使用 GET 之外的方法访问,请求会被拦截并返回 401 状态码。
-2. 使用 `GET` 请求 `/hello`,在响应中会有一个携带了加密 `token` 的 `Cookie`。Token 字段的名字为插件配置中的 `name` 值,如果没有配置的话默认值为 `apisix_csrf_token`。
+2. 使用 `GET` 请求 `/hello`,在响应中会有一个携带了加密 `token` 的 `Cookie`。Token 字段的名字为插件配置中的 `name` 值,如果没有配置的话默认值为 `apisix-csrf-token`。
Review comment:
```suggestion
2. 使用 `GET` 请求 `/hello`,在响应中会有一个携带了加密 `token` 的 `Cookie`。Token 字段的名字为插件配置中的 `name` 值,如果没有配置该值,那么默认值为 `apisix-csrf-token`。
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r780922140
##########
File path: t/plugin/csrf.t
##########
@@ -0,0 +1,154 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local plugin = require("apisix.plugins.csrf")
+ local ok, err = plugin.check_schema({name = '_csrf', expires = 3600, key = 'testkey'})
+ if not ok then
+ ngx.say(err)
+ end
+
+ ngx.say("done")
+ }
+ }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: set csrf plugin
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "csrf": {
+ "key": "userkey"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: have csrf cookie
+--- request
+GET /hello
+--- response_header
+Set-Cookie
Review comment:
We can verify part of the value with https://metacpan.org/pod/Test%3A%3ANginx%3A%3ASocket#response_headers_like
##########
File path: t/plugin/csrf.t
##########
@@ -0,0 +1,154 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+run_tests();
+
Review comment:
Let's add https://github.com/apache/apisix/blob/e481fc7db8b97b41389cef0ce2b978a7fc0bbe2b/t/plugin/opa.t#L23 and remove duplicated `--- request`.
##########
File path: t/plugin/csrf.t
##########
@@ -0,0 +1,154 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local plugin = require("apisix.plugins.csrf")
+ local ok, err = plugin.check_schema({name = '_csrf', expires = 3600, key = 'testkey'})
+ if not ok then
+ ngx.say(err)
+ end
+
+ ngx.say("done")
+ }
+ }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: set csrf plugin
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "csrf": {
+ "key": "userkey"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: have csrf cookie
+--- request
+GET /hello
+--- response_header
+Set-Cookie
+
+
+
+=== TEST4: block request
+--- request
+POST /hello
+--- error_code: 401
+--- response_body
+{"error_msg":"no csrf token in headers"}
+--- no_error_log
+[error]
+
+
+
+=== TEST5: only header
+--- request
+POST /hello
+--- more_headers
+apisix-csrf-token: wrongtoken
+--- error_code: 401
+--- response_body
+{"error_msg":"no csrf cookie"}
+--- no_error_log
+[error]
+
+
+
+=== TEST6: only cookie
+--- request
+POST /hello
+--- more_headers
+Cookie: apisix-csrf-token=testcookie
+--- error_code: 401
+--- response_body
+{"error_msg":"no csrf token in headers"}
+--- no_error_log
+[error]
+
+
+
+=== TEST7: header and cookie mismatch
+--- request
+POST /hello
+--- more_headers
+apisix-csrf-token: wrongtoken
+Cookie: apisix-csrf-token=testcookie
+--- error_code: 401
+--- response_body
+{"error_msg":"csrf token mismatch"}
+--- no_error_log
+[error]
+
+
+
+=== TEST8: invalid csrf token
Review comment:
Let's add a test which token matches the header
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r784711246
##########
File path: t/plugin/csrf.t
##########
@@ -0,0 +1,162 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!defined $block->request) {
+ $block->set_value("request", "GET /t");
+ }
+
+ if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+ $block->set_value("no_error_log", "[error]");
+ }
+
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local plugin = require("apisix.plugins.csrf")
+ local ok, err = plugin.check_schema({name = '_csrf', expires = 3600, key = 'testkey'})
+ if not ok then
+ ngx.say(err)
+ end
+
+ ngx.say("done")
+ }
+ }
+--- response_body
+done
+
+
+
+=== TEST 2: set csrf plugin
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "csrf": {
+ "key": "userkey"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 3: have csrf cookie
+--- request
+GET /hello
+--- response_headers_like
+Set-Cookie: apisix-csrf-token\s*=\s*[^;]+(.*)?$
+
+
+
+=== TEST4: block request
Review comment:
Let's add a space before the number
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] leslie-tsang commented on pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#issuecomment-995569788
> Would you please take a look when you have time? Thanks.
Hello there, Would you mind to pass the CI test first ?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r776914120
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in request header"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
Review comment:
OPTIONS has been added to take into account cross-domain requests.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] tzssangglass commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r775945667
##########
File path: docs/zh/latest/plugins/csrf.md
##########
@@ -0,0 +1,127 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## 目录
+
+- [**简介**](#简介)
+- [**属性**](#属性)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+## 简介
+
+`CSRF` 插件基于 `Double Submit Cookie` 的方式,保护您的 API 免于 CSRF 攻击。
+
+## 属性
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | false | `apisix_csrf_token` | | 生成的 Cookie 中的 token 的名字,需要使用这个名字从 Cookie 中读取 |
Review comment:
`需要使用这个名字从 Cookie 中读取` is strange
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785710009
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Please open an issue for keeping track of the progress.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r768688847
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,169 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local request_method = ngx.var.request_method
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local ck = require "resty.cookie"
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
Review comment:
It is used to encrypt the token to prevent it from being forged.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#issuecomment-995462000
Would you please take a look when you have time? Thanks.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r777440057
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,133 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | false | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | false | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | true | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+:::important
+
+A new cookie is generated for each request.
+
+:::
+
+3. In subsequent non-GET requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+The above configuration created a `/hello` route and enabled the csrf plugin, now direct access to the route using a non-GET method will return an error:
Review comment:
We don't use the past tense in the example.
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,133 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | false | `apisix-csrf-token` | | The name of the token in the generated cookie. |
Review comment:
We don't use `true` or `false` for the requirement. Please see https://github.com/apache/apisix/blob/master/docs/en/latest/plugins/gzip.md
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,133 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | false | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | false | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | true | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+:::important
+
+A new cookie is generated for each request.
+
+:::
+
+3. In subsequent non-GET requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+The above configuration created a `/hello` route and enabled the csrf plugin, now direct access to the route using a non-GET method will return an error:
+
+```
+curl -i http://127.0.0.1:9080/hello -X POST
+```
+
+```
+HTTP/1.1 401 Unauthorized
Review comment:
We can only show the status code?
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,133 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | false | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | false | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | true | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+:::important
+
+A new cookie is generated for each request.
+
+:::
+
+3. In subsequent non-GET requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+The above configuration created a `/hello` route and enabled the csrf plugin, now direct access to the route using a non-GET method will return an error:
+
+```
+curl -i http://127.0.0.1:9080/hello -X POST
+```
+
+```
Review comment:
We don't split the output and the command in other plugins.
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is null")
+ return false
+ end
+
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method(ctx)
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in headers"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local csrf_token = gen_csrf_token(conf)
Review comment:
This problem is not addressed?
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,162 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method(ctx)
+ if method == 'GET' or 'OPTIONS' then
Review comment:
Why exclude `OPTIONS`?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r786447523
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Thank you. Merging.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#issuecomment-1016006413
> @Baoyuantop please add this plugin to README.md too
Done https://github.com/apache/apisix/pull/6144
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r782747480
##########
File path: t/plugin/csrf.t
##########
@@ -0,0 +1,170 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!defined $block->request) {
+ $block->set_value("request", "GET /t");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local plugin = require("apisix.plugins.csrf")
+ local ok, err = plugin.check_schema({name = '_csrf', expires = 3600, key = 'testkey'})
+ if not ok then
+ ngx.say(err)
+ end
+
+ ngx.say("done")
+ }
+ }
+--- response_body
+done
+--- no_error_log
Review comment:
We can do the same with `--- no_error_log` too?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785396536
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,136 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks. This plugin considers the `GET`, `HEAD` and `OPTIONS` methods to be safe operations. Therefore calls to the `GET`, `HEAD` and `OPTIONS` methods are not checked for interception.
Review comment:
I guess it would be better to add a hyperlink at Double Submit Cookie. https://en.wikipedia.org/wiki/Cross-site_request_forgery#Double_Submit_Cookie
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
Review comment:
Please localize the method as from resty.string module we are using this single method.
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
Review comment:
For code style consistency can we use `ngx_cookie_time` instead of `cookie_time`?
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,136 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks. This plugin considers the `GET`, `HEAD` and `OPTIONS` methods to be safe operations. Therefore calls to the `GET`, `HEAD` and `OPTIONS` methods are not checked for interception.
+
+In the following we define `GET`, `HEAD` and `OPTIONS` as the `safe-methods` and those other than these as `unsafe-methods`.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | optional | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | optional | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | required | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```shell
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+Please note: We return a new cookie for each request.
+
+3. In subsequent unsafe-methods requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+Direct access to the '/hello' route using a `POST` method will return an error:
+
+```shell
+curl -i http://127.0.0.1:9080/hello -X POST
+
+HTTP/1.1 401 Unauthorized
+...
+{"error_msg":"no csrf token in headers"}
+```
+
+When accessed with a GET request, the correct return and a cookie with an encrypted token are obtained:
+
+```shell
+curl -i http://127.0.0.1:9080/hello
+
+HTTP/1.1 200 OK
+Set-Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ==;path=/;Expires=Mon, 13-Dec-21 09:33:55 GMT
+```
+
+The token needs to be read from the cookie and carried in the request header in subsequent unsafe-methods requests.
+
+For example, use [js-cookie](https://github.com/js-cookie/js-cookie) read cookie and [axios](https://github.com/axios/axios) send request in client:
+
+```js
+const token = Cookie.get('apisix-csrf-token');
+
+const instance = axios.create({
+ headers: {'apisix-csrf-token': token}
+});
+```
+
+You also need to make sure that you carry the cookie.
+
+Use curl send request:
+
+```shell
+curl -i http://127.0.0.1:9080/hello -X POST -H 'apisix-csrf-token: eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ==' -b 'apisix-csrf-token=eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ=='
+
+HTTP/1.1 200 OK
+```
+
+## Disable Plugin
+
+Send a request to update the route to disable the plugin:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+}'
+```
+
+CSRF plugin have been disabled.
Review comment:
```suggestion
The CSRF plugin has been disabled.
```
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if token_table["sign"] ~= sign then
+ core.log.error("Invalid signatures")
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local safe_methods = {"GET", "HEAD", "OPTIONS"}
Review comment:
Can we extract this out from the access phase and treat it as a constant array?
`local SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785417944
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Just a proposal here, in the token object instead of total expiration seconds, can we use the expiration timestamp? So during the checking phase inside the `check_csrf_token` method, we could potentially check/prevent/[minimize the window] of replay attacks as an added benefit by figuring out if the generated cookie is long been expired or not. WDYT
Thank you. cc @spacewander
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if token_table["sign"] ~= sign then
+ core.log.error("Invalid signatures")
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local safe_methods = {"GET", "HEAD", "OPTIONS"}
+ local method = core.request.get_method(ctx)
+ if core.table.array_find(safe_methods, method) then
+ return
+ end
+
+ local header_token = core.request.header(ctx, conf.name)
+ if not header_token or header_token == "" then
+ return 401, {error_msg = "no csrf token in headers"}
+ end
+
+ local cookie_token = ctx.var["cookie_" .. conf.name]
+ if not cookie_token then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if header_token ~= cookie_token then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, cookie_token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local csrf_token = gen_csrf_token(conf)
+ local cookie = conf.name .. "=" .. csrf_token .. ";path=/;SameSite=Lax;Expires="
+ .. cookie_time(ngx_time() + conf.expires)
Review comment:
Instead of `Expires` I think we can use `Max-Age` attribute here. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
Since, the operation is performed for each request, at the long rung it will save some computation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785436752
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Yes. Thanks for @bisakhmondal 's suggestion.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] moonming commented on pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
moonming commented on pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#issuecomment-1015253594
@Baoyuantop please add this plugin to README.md too
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r786428808
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Done @bisakhmondal. https://github.com/apache/apisix/issues/6141
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] juzhiyuan commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
juzhiyuan commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r763978097
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,150 @@
+local core = require("apisix.core")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ck = require "resty.cookie"
+local math = math
+
+local lrucache = core.lrucache.new({
+ type = "plugin",
+})
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
Review comment:
Format your codes?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r779246603
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,125 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks. This plugin considers the `GET`, `HEAD` and `OPTIONS` methods to be safe operations. Therefore calls to the `GET`, `HEAD` and `OPTIONS` methods are not checked for interception.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | optional | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | optional | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | required | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+Please note: We return a new cookie for each request.
+
+3. In subsequent non-GET requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+Direct access to the '/hello' route using a `POST` method will return an error:
+
+```
+curl -i http://127.0.0.1:9080/hello -X POST
+
+HTTP/1.1 401
Review comment:
We can only show the 401 status code?
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,125 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks. This plugin considers the `GET`, `HEAD` and `OPTIONS` methods to be safe operations. Therefore calls to the `GET`, `HEAD` and `OPTIONS` methods are not checked for interception.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | optional | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | optional | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | required | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+Please note: We return a new cookie for each request.
+
+3. In subsequent non-GET requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+Direct access to the '/hello' route using a `POST` method will return an error:
+
+```
+curl -i http://127.0.0.1:9080/hello -X POST
+
+HTTP/1.1 401
+Date: Mon, 13 Dec 2021 07:23:23 GMT
+Content-Type: text/plain; charset=utf-8
+Transfer-Encoding: chunked
+Connection: keep-alive
+Server: APISIX
+```
+
+When accessed with a GET request, the correct return and a cookie with an encrypted token are obtained:
+
+```
+curl -i http://127.0.0.1:9080/hello
+
+HTTP/1.1 200
Review comment:
In this case, we can only show 200 and the `Set-Cookie` header
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,125 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks. This plugin considers the `GET`, `HEAD` and `OPTIONS` methods to be safe operations. Therefore calls to the `GET`, `HEAD` and `OPTIONS` methods are not checked for interception.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | optional | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | optional | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | required | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+Please note: We return a new cookie for each request.
+
+3. In subsequent non-GET requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+Direct access to the '/hello' route using a `POST` method will return an error:
+
+```
Review comment:
Let's add `shell` like the other plugin doc
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,125 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks. This plugin considers the `GET`, `HEAD` and `OPTIONS` methods to be safe operations. Therefore calls to the `GET`, `HEAD` and `OPTIONS` methods are not checked for interception.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | optional | `apisix-csrf-token` | | The name of the token in the generated cookie. |
+| expires | number | optional | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | required | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
+{
+ "uri": "/hello",
+ "plugins": {
+ "csrf": {
+ "key": "edd1c9f034335f136f87ad84b625c8f1"
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:9001": 1
+ }
+ }
+}'
+```
+
+The route is then protected, and if you access it using methods other than `GET`, you will see that the request was blocked and receive a 401 status code back.
+
+2. Using `GET` requests `/hello`, a cookie with an encrypted token is received in the response. Token name is the `name` field set in the plugin configuration, if not set, the default value is `apisix-csrf-token`.
+
+Please note: We return a new cookie for each request.
+
+3. In subsequent non-GET requests to this route, you need to read the encrypted token from the cookie and append the token to the `request header`, setting the field name to the `name` in the plugin configuration.
+
+## Test Plugin
+
+Direct access to the '/hello' route using a `POST` method will return an error:
+
+```
+curl -i http://127.0.0.1:9080/hello -X POST
+
+HTTP/1.1 401
+Date: Mon, 13 Dec 2021 07:23:23 GMT
+Content-Type: text/plain; charset=utf-8
+Transfer-Encoding: chunked
+Connection: keep-alive
+Server: APISIX
+```
+
+When accessed with a GET request, the correct return and a cookie with an encrypted token are obtained:
+
+```
+curl -i http://127.0.0.1:9080/hello
+
+HTTP/1.1 200
+Content-Type: text/plain; charset=utf-8
+Content-Length: 13
+Connection: keep-alive
+x-content-type-options: nosniff
+x-frame-options: SAMEORIGIN
+permissions-policy: interest-cohort=()
+date: Mon, 13 Dec 2021 07:33:55 GMT
+Server: APISIX
+Set-Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ==;path=/;Expires=Mon, 13-Dec-21 09:33:55 GMT
+```
+
+The token needs to be read from the cookie and carried in the request header in subsequent non-GET requests. You also need to make sure that you carry the cookie.
Review comment:
Better to add an example
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r786449243
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local ngx_cookie_time = ngx.cookie_time
+local math = math
+local SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
Review comment:
```suggestion
if not token_str then
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785424673
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
Review comment:
Agree
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785641183
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Hi @bisakhmondal, I would prefer a separate PR to solve this problem, this PR is already a bit big.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r774296400
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
Review comment:
Here a port is needed for getting the csrf token, and if all of them are blocked, the cookie may not be placed. Therefore, CORS on this port should be configured carefully, as I will explain in the documentation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] spacewander commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r776543710
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is null")
+ return false
+ end
+
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method(ctx)
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in headers"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local csrf_token = gen_csrf_token(conf)
+ local cookie = conf.name .. "=" .. csrf_token .. ";path=/;Expires="
+ .. cookie_time(ngx_time() + conf.expires)
+ core.response.add_header("Set-Cookie", cookie)
+end
+
Review comment:
Ditto
##########
File path: docs/en/latest/plugins/csrf.md
##########
@@ -0,0 +1,127 @@
+---
+title: csrf
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Summary
+
+- [**Description**](#description)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Description
+
+The `CSRF` plugin based on the `Double Submit Cookie` way, protect your API from CSRF attacks.
+
+## Attributes
+
+| Name | Type | Requirement | Default | Valid | Description |
+| ---------------- | ------- | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| name | string | false | `apisix_csrf_token` | | The name of the token in the generated cookie. |
+| expires | number | false | `7200` | | Expiration time(s) of csrf cookie. |
+| key | string | true | | | The secret key used to encrypt the cookie. |
+
+## How To Enable
+
+1. Create the route and enable the plugin.
+
+```
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X POST -d '
Review comment:
```suggestion
curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT-d '
```
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is null")
Review comment:
The error message is incorrect. Also, better to avoid using `_token` & `token`, it is quite confusing about the meaning of the `_` prefix.
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
Review comment:
Please use two blank lines to separate each section (localized functions, define module-level variables, define functions, return _M)
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is null")
+ return false
+ end
+
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method(ctx)
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in headers"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local csrf_token = gen_csrf_token(conf)
+ local cookie = conf.name .. "=" .. csrf_token .. ";path=/;Expires="
Review comment:
Should we set the SameSite field for the cookie?
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is null")
+ return false
+ end
+
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method(ctx)
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in headers"}
+ end
+
+ local cookie, err = ck:new()
+ if not cookie then
+ return nil, err
+ end
+
+ local field_cookie, err = cookie:get(conf.name)
+ if not field_cookie then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if err then
+ core.log.error(err)
+ return 400, {error_msg = "read csrf cookie failed"}
+ end
+
+ if token ~= field_cookie then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local csrf_token = gen_csrf_token(conf)
Review comment:
Better to emphasize in the doc that a new cookie is generated for each request
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] Baoyuantop commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
Baoyuantop commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r777898436
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,168 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local timer = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 3500,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random();
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.warn("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.warn("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = ctx.var.request_method
+ if method == 'GET' then
Review comment:
This type of request has no side effects and does not need to accept protection, but of course, I will add the corresponding instructions in the documentation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal merged pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal merged pull request #5727:
URL: https://github.com/apache/apisix/pull/5727
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785417465
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if token_table["sign"] ~= sign then
+ core.log.error("Invalid signatures")
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local safe_methods = {"GET", "HEAD", "OPTIONS"}
+ local method = core.request.get_method(ctx)
+ if core.table.array_find(safe_methods, method) then
+ return
+ end
+
+ local header_token = core.request.header(ctx, conf.name)
+ if not header_token or header_token == "" then
+ return 401, {error_msg = "no csrf token in headers"}
+ end
+
+ local cookie_token = ctx.var["cookie_" .. conf.name]
+ if not cookie_token then
+ return 401, {error_msg = "no csrf cookie"}
+ end
+
+ if header_token ~= cookie_token then
+ return 401, {error_msg = "csrf token mismatch"}
+ end
+
+ local result = check_csrf_token(conf, ctx, cookie_token)
+ if not result then
+ return 401, {error_msg = "Failed to verify the csrf token signature"}
+ end
+end
+
+
+function _M.header_filter(conf, ctx)
+ local csrf_token = gen_csrf_token(conf)
+ local cookie = conf.name .. "=" .. csrf_token .. ";path=/;SameSite=Lax;Expires="
+ .. cookie_time(ngx_time() + conf.expires)
Review comment:
Instead of `Expires` I think we can use `Max-Age` attribute here. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
Since, the operation is performed for each request, at the long run it will save some computation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] bisakhmondal commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
bisakhmondal commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r785636480
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,161 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local str = require("resty.string")
+local ngx = ngx
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix-csrf-token"
+ }
+ },
+ required = {"key"}
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = "csrf",
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = "{expires:" .. expires .. ",random:" .. random .. ",key:" .. key .. "}"
+
+ sha256:update(sign)
+ local digest = sha256:final()
+
+ return str.to_hex(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local token_str = ngx_decode_base64(token)
+ if token_str == nil then
+ core.log.error("csrf token base64 decode error")
+ return false
+ end
+
+ local token_table, err = core.json.decode(token_str)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
Review comment:
Hey @Baoyuantop, thanks for making the changes. Are you going to address this in this PR or do you think it would be better to have it on a separate PR? Thank you.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [apisix] tzssangglass commented on a change in pull request #5727: feat: add CSRF plugin
Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #5727:
URL: https://github.com/apache/apisix/pull/5727#discussion_r775946413
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is nil")
+ return false
+ end
+
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method
Review comment:
`local method = core.request.get_method(ctx)` ?
##########
File path: apisix/plugins/csrf.lua
##########
@@ -0,0 +1,170 @@
+--
+-- 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.
+--
+local core = require("apisix.core")
+local resty_sha256 = require("resty.sha256")
+local ck = require("resty.cookie")
+local ngx = ngx
+local plugin_name = "csrf"
+local ngx_encode_base64 = ngx.encode_base64
+local ngx_decode_base64 = ngx.decode_base64
+local ngx_time = ngx.time
+local cookie_time = ngx.cookie_time
+local math = math
+
+local schema = {
+ type = "object",
+ properties = {
+ key = {
+ description = "use to generate csrf token",
+ type = "string",
+ },
+ expires = {
+ description = "expires time(s) for csrf token",
+ type = "integer",
+ default = 7200
+ },
+ name = {
+ description = "the csrf token name",
+ type = "string",
+ default = "apisix_csrf_token"
+ }
+ },
+ required = {"key"}
+}
+
+local _M = {
+ version = 0.1,
+ priority = 2980,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+local function gen_sign(random, expires, key)
+ local sha256 = resty_sha256:new()
+
+ local sign = {
+ random = random,
+ expires = expires,
+ key = key,
+ }
+
+ sha256:update(core.json.encode(sign))
+ local digest = sha256:final()
+
+ return ngx_encode_base64(digest)
+end
+
+
+local function gen_csrf_token(conf)
+ local random = math.random()
+ local sign = gen_sign(random, conf.expires, conf.key)
+
+ local token = {
+ random = random,
+ expires = conf.expires,
+ sign = sign,
+ }
+
+ local cookie = ngx_encode_base64(core.json.encode(token))
+ return cookie
+end
+
+
+local function check_csrf_token(conf, ctx, token)
+ local _token = ngx_decode_base64(token)
+ if _token == nil then
+ core.log.error("csrf token is nil")
+ return false
+ end
+
+ local _token_table, err = core.json.decode(_token)
+ if err then
+ core.log.error("decode token error: ", err)
+ return false
+ end
+
+ local random = _token_table["random"]
+ if not random then
+ core.log.error("no random in token")
+ return false
+ end
+
+ local expires = _token_table["expires"]
+ if not expires then
+ core.log.error("no expires in token")
+ return false
+ end
+
+ local sign = gen_sign(random, expires, conf.key)
+ if _token_table["sign"] ~= sign then
+ return false
+ end
+
+ return true
+end
+
+
+function _M.access(conf, ctx)
+ local method = core.request.get_method
+ if method == 'GET' then
+ return
+ end
+
+ local token = core.request.header(ctx, conf.name)
+ if not token then
+ return 401, {error_msg = "no csrf token in request header"}
Review comment:
```suggestion
return 401, {error_msg = "no csrf token in headers"}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org