Skip to content

feat: add CSRF plugin#5727

Merged
bisakhmondal merged 29 commits into
apache:masterfrom
Baoyuantop:feat-csrf
Jan 18, 2022
Merged

feat: add CSRF plugin#5727
bisakhmondal merged 29 commits into
apache:masterfrom
Baoyuantop:feat-csrf

Conversation

@Baoyuantop

@Baoyuantop Baoyuantop commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Add CSRF plugin for APISIX
Discuss in https://lists.apache.org/thread/ddj53svylg9s541vf2gpysxwgyrhwlzx

Pre-submission checklist:

  • 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 first

Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
@Baoyuantop
Baoyuantop marked this pull request as ready for review December 14, 2021 13:56
@Baoyuantop

Copy link
Copy Markdown
Contributor Author

Would you please take a look when you have time? Thanks.

@leslie-tsang

Copy link
Copy Markdown
Member

Would you please take a look when you have time? Thanks.

Hello there, Would you mind to pass the CI test first ?

@spacewander spacewander left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use 4 spaces to indent and remove trailing ';'

Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated

function _M.access(conf, ctx)
local method = ctx.var.request_method
if method == 'GET' then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why skip GET?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread apisix/plugins/csrf.lua Outdated

function _M.header_filter(conf, ctx)
local method = ctx.var.request_method
if method == 'GET' then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only set cookie for GET?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OPTIONS has been added to take into account cross-domain requests.

Comment thread apisix/plugins/csrf.lua Outdated
Comment thread conf/config-default.yaml Outdated
Comment thread docs/zh/latest/plugins/csrf.md Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua
Comment thread apisix/plugins/csrf.lua
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread docs/en/latest/plugins/csrf.md Outdated
Comment thread apisix/plugins/csrf.lua Outdated
Comment thread apisix/plugins/csrf.lua


function _M.header_filter(conf, ctx)
local csrf_token = gen_csrf_token(conf)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to emphasize in the doc that a new cookie is generated for each request

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem is not addressed?

Comment thread docs/zh/latest/plugins/csrf.md Outdated

@spacewander spacewander left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread t/plugin/csrf.t
no_shuffle();
no_root_location();
run_tests();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add

add_block_preprocessor(sub {
and remove duplicated --- request.

Comment thread t/plugin/csrf.t Outdated
--- request
GET /hello
--- response_header
Set-Cookie

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread t/plugin/csrf.t Outdated



=== TEST8: invalid csrf token

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a test which token matches the header

Comment thread t/plugin/csrf.t Outdated
}
--- response_body
done
--- no_error_log

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do the same with --- no_error_log too?

Comment thread t/plugin/csrf.t Outdated



=== TEST4: block request

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a space before the number

@bisakhmondal bisakhmondal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm%(some nitpicking)
Thank you.

Comment thread apisix/plugins/csrf.lua Outdated
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For code style consistency can we use ngx_cookie_time instead of cookie_time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

Comment thread apisix/plugins/csrf.lua
sha256:update(sign)
local digest = sha256:final()

return str.to_hex(digest)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please localize the method as from resty.string module we are using this single method.

Comment thread apisix/plugins/csrf.lua Outdated


function _M.access(conf, ctx)
local safe_methods = {"GET", "HEAD", "OPTIONS"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract this out from the access phase and treat it as a constant array?
local SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}

Comment thread docs/en/latest/plugins/csrf.md Outdated

## 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread docs/en/latest/plugins/csrf.md Outdated
}'
```

CSRF plugin have been disabled.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CSRF plugin have been disabled.
The CSRF plugin has been disabled.

@juzhiyuan juzhiyuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread apisix/plugins/csrf.lua Outdated
Comment on lines +155 to +156
local cookie = conf.name .. "=" .. csrf_token .. ";path=/;SameSite=Lax;Expires="
.. cookie_time(ngx_time() + conf.expires)

@bisakhmondal bisakhmondal Jan 16, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread apisix/plugins/csrf.lua
Comment on lines +79 to +113
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Thanks for @bisakhmondal 's suggestion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @bisakhmondal, I would prefer a separate PR to solve this problem, this PR is already a bit big.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Sounds good to me. Thanks

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please open an issue for keeping track of the progress.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Merging.

Comment thread apisix/plugins/csrf.lua Outdated
@bisakhmondal
bisakhmondal merged commit 4d52c3e into apache:master Jan 18, 2022
@Baoyuantop
Baoyuantop deleted the feat-csrf branch January 18, 2022 08:26
@moonming

Copy link
Copy Markdown
Member

@Baoyuantop please add this plugin to README.md too

@Baoyuantop

Copy link
Copy Markdown
Contributor Author

@Baoyuantop please add this plugin to README.md too

Done #6144

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants