feat: add CSRF plugin#5727
Conversation
|
Would you please take a look when you have time? Thanks. |
Hello there, Would you mind to pass the CI test first ? |
spacewander
left a comment
There was a problem hiding this comment.
Let's use 4 spaces to indent and remove trailing ';'
|
|
||
| function _M.access(conf, ctx) | ||
| local method = ctx.var.request_method | ||
| if method == 'GET' then |
There was a problem hiding this 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.
There was a problem hiding this 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.
|
|
||
| function _M.header_filter(conf, ctx) | ||
| local method = ctx.var.request_method | ||
| if method == 'GET' then |
There was a problem hiding this comment.
Why only set cookie for GET?
There was a problem hiding this comment.
OPTIONS has been added to take into account cross-domain requests.
|
|
||
|
|
||
| function _M.header_filter(conf, ctx) | ||
| local csrf_token = gen_csrf_token(conf) |
There was a problem hiding this comment.
Better to emphasize in the doc that a new cookie is generated for each request
There was a problem hiding this comment.
This problem is not addressed?
Co-authored-by: 琚致远 <[email protected]>
spacewander
left a comment
There was a problem hiding this comment.
Great. Let's add some tests.
You can refer to
https://github.com/apache/apisix/blob/master/docs/en/latest/internal/testing-framework.md
https://github.com/apache/apisix/blob/master/t/plugin/opa.t
| no_shuffle(); | ||
| no_root_location(); | ||
| run_tests(); | ||
|
|
There was a problem hiding this comment.
| --- request | ||
| GET /hello | ||
| --- response_header | ||
| Set-Cookie |
There was a problem hiding this comment.
We can verify part of the value with https://metacpan.org/pod/Test%3A%3ANginx%3A%3ASocket#response_headers_like
|
|
||
|
|
||
|
|
||
| === TEST8: invalid csrf token |
There was a problem hiding this comment.
Let's add a test which token matches the header
| } | ||
| --- response_body | ||
| done | ||
| --- no_error_log |
There was a problem hiding this comment.
We can do the same with --- no_error_log too?
|
|
||
|
|
||
|
|
||
| === TEST4: block request |
There was a problem hiding this comment.
Let's add a space before the number
bisakhmondal
left a comment
There was a problem hiding this comment.
lgtm%(some nitpicking)
Thank you.
| 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 |
There was a problem hiding this comment.
For code style consistency can we use ngx_cookie_time instead of cookie_time?
| sha256:update(sign) | ||
| local digest = sha256:final() | ||
|
|
||
| return str.to_hex(digest) |
There was a problem hiding this comment.
Please localize the method as from resty.string module we are using this single method.
|
|
||
|
|
||
| function _M.access(conf, ctx) | ||
| local safe_methods = {"GET", "HEAD", "OPTIONS"} |
There was a problem hiding this comment.
Can we extract this out from the access phase and treat it as a constant array?
local SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
|
|
||
| ## 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. |
There was a problem hiding this 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
| }' | ||
| ``` | ||
|
|
||
| CSRF plugin have been disabled. |
There was a problem hiding this comment.
| CSRF plugin have been disabled. | |
| The CSRF plugin has been disabled. |
| local cookie = conf.name .. "=" .. csrf_token .. ";path=/;SameSite=Lax;Expires=" | ||
| .. cookie_time(ngx_time() + conf.expires) |
There was a problem hiding this 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.
| 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 |
There was a problem hiding this 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
There was a problem hiding this 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.
There was a problem hiding this comment.
Hi @bisakhmondal, I would prefer a separate PR to solve this problem, this PR is already a bit big.
There was a problem hiding this comment.
Sure. Sounds good to me. Thanks
There was a problem hiding this comment.
Please open an issue for keeping track of the progress.
Co-authored-by: Bisakh <[email protected]>
|
@Baoyuantop please add this plugin to README.md too |
Done #6144 |
What this PR does / why we need it:
Add CSRF plugin for APISIX
Discuss in https://lists.apache.org/thread/ddj53svylg9s541vf2gpysxwgyrhwlzx
Pre-submission checklist: