Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(oauth2): prevent an authorization code created by one plugin instance to be exchanged for an access token by a different plugin instance #10011

Merged

Conversation

vm-001
Copy link
Contributor

@vm-001 vm-001 commented Dec 28, 2022

Summary

Forbid an authorization code can be exchanged to access_token in different plugin instance.

Checklist

Issue reference

FTI-3983

@vm-001
Copy link
Contributor Author

vm-001 commented Dec 28, 2022

Related PR: #9312

@vm-001 vm-001 force-pushed the fix/oauth2-code-exchange-across-plugin-instances branch from 8af6631 to f4d82a7 Compare December 28, 2022 04:08
@vm-001 vm-001 requested a review from fffonion December 28, 2022 04:09
@vm-001 vm-001 added this to the 3.2 milestone Dec 28, 2022
@@ -2360,7 +2374,7 @@ describe("Plugin: oauth2 [#" .. strategy .. "]", function()
assert.same({ error_description = "code_verifier is required for PKCE authorization requests", error = "invalid_request" }, json)
end)
it("success when no code_verifier provided for public app without pkce when conf.pkce is none", function()
local code = provision_code()
local code = provision_code("oauth2_14.com")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fix the test to use the same oauth2 plugin instance

Copy link
Contributor

@hanshuebner hanshuebner left a comment

Choose a reason for hiding this comment

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

Some grammar and spelling issues to be fixed.

kong/plugins/oauth2/access.lua Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
@vm-001 vm-001 force-pushed the fix/oauth2-code-exchange-across-plugin-instances branch from d4708e5 to c7cd10e Compare January 4, 2023 02:37
@vm-001 vm-001 changed the title fix(oauth2): forbid an authorization code can be exchanged to access_token in different plugin instance fix(oauth2): prevent an authorization code created by one plugin instance to be exchanged for an access token by a different plugin instance Jan 4, 2023
@flrgh flrgh dismissed hanshuebner’s stale review February 1, 2023 20:45

requested changes were made

kong/plugins/oauth2/access.lua Outdated Show resolved Hide resolved
kikito
kikito previously requested changes Feb 1, 2023
kong/plugins/oauth2/access.lua Outdated Show resolved Hide resolved
kong/plugins/oauth2/access.lua Outdated Show resolved Hide resolved
@jschmid1 jschmid1 self-requested a review February 2, 2023 11:13
@vm-001 vm-001 force-pushed the fix/oauth2-code-exchange-across-plugin-instances branch 2 times, most recently from c844358 to 1a047a0 Compare April 12, 2023 07:32
@hbagdi
Copy link
Member

hbagdi commented Apr 17, 2023

This is most certainly a breaking change since it is very much possible that a user could be relying on such buggy behavior.
Please do the due diligence on the impact of it and make the decision, @kikito.
And if we decide to proceed, please call it out in changelog to alert the user. If they find this in production, they will be quite sour (for good reasons).

@vm-001
Copy link
Contributor Author

vm-001 commented Apr 23, 2023

@hbagdi @kikito I can add an entry to the "breaking change" section to warning this behavior change. Could this solve your concern?

@@ -79,6 +79,7 @@ local oauth2_authorization_codes = {
{ scope = { type = "string" }, },
{ challenge = { type = "string", required = false }},
{ challenge_method = { type = "string", required = false, one_of = { "S256" } }},
{ plugin_id = { type = "string", required = false } },
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we make this a foreign key?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, and sounds good to me.

@@ -643,6 +644,15 @@ local function issue_token(conf)
end
end

if not response_params[ERROR] and conf.global_credentials then
if kong.plugin.get_id() ~= auth_code.plugin_id then
Copy link
Contributor

Choose a reason for hiding this comment

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

How do we handle entries added before the migration?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that would be a problem. I updated it to become only validate if auth_code.plugin_id is present to make the old code pass the validation.

@hbagdi
Copy link
Member

hbagdi commented Apr 25, 2023

Discussed this today with @kikito. He will circle back on this one.

@hanshuebner
Copy link
Contributor

hanshuebner commented May 3, 2023

@hbagdi I agree with @vm-001 that the current behavior is buggy. I cannot imagine a customer relying on the current behavior. As a reminder, here is a depiction of the OAuth2 authorization flow:

image

The user first creates an authorization grant (authentication code) using the service of the OAuth2 identity provider. It then presents the authentication code to the authorization server, which exchanges it for an access token. That token can then be used to authenticate against the resource server.

I believe that we can assume that our users use the OAuth2 plugin in the "normal way": A user hits a service that is protected by OAuth2 and that redirects them to the IdP. The IdP issues the authentication code and redirects the user back to the same service, in which Kong plays the authentication server role. Kong exchanges the authentication code for an access token and returns it to the client for further interaction with the resource server.

I cannot imagine a situation in which a user would willingly want to be able to have Kong exchange the authentication code issued when trying to access one service by another service. It seems to me that this would only happen if there is an error in the application (i.e. the OAuth2 related responses are not properly interpreted, making the token request go to a different service) or by malicious intent (i.e. an attacker has access to a less-privileged service and uses the bug to exchange an authentication code by that less-privileged service to a token issued by a more-privileged service).

I am not confident that the fix in this PR will solve all such problems, but I'm certain that we don't want to leave such gaps open if we know about them.

The PR does not currently pass checks because of an incompatibility with Cassandra. This will need to be fixed. I also have the question whether config.global_credentials does not imply that Kong treats access tokens from one plugin to be equivalent to access tokens from other plugins. If that is the case, then how does this change actually improve the situation? Would we not need to say that if config.global_credentials is true, all plugins that share credentials need to be configured identically anyway?

@vm-001 vm-001 force-pushed the fix/oauth2-code-exchange-across-plugin-instances branch from b75551b to 8693bc8 Compare May 5, 2023 09:55
@vm-001 vm-001 force-pushed the fix/oauth2-code-exchange-across-plugin-instances branch from 8693bc8 to a86239d Compare May 8, 2023 06:51
@vm-001
Copy link
Contributor Author

vm-001 commented May 8, 2023

I also have the question whether config.global_credentials does not imply that Kong treats access tokens from one plugin to be equivalent to access tokens from other plugins. If that is the case, then how does this change actually improve the situation? Would we not need to say that if config.global_credentials is true, all plugins that share credentials need to be configured identically anyway?

I think the config.global_credentials is a setting indicating whether allow an oauth2 access token(not code) to across accessing different services when they both have set global_credentials to true. I'm not seeing Kong has any test cases against that if it was considered as a feature

@hanshuebner hanshuebner dismissed kikito’s stale review May 8, 2023 08:28

Questions have been addressed

@hanshuebner hanshuebner merged commit 8c4e5c6 into master May 8, 2023
@hanshuebner hanshuebner deleted the fix/oauth2-code-exchange-across-plugin-instances branch May 8, 2023 08:29
team-gateway-bot pushed a commit that referenced this pull request May 8, 2023
…ance to be exchanged for an access token by a different plugin instance (#10011)

* fix(oauth2): prevent an authorization code created by one plugin instance to be exchanged for an access token by a different plugin instance

* verify only if plugin_id is present to avoid existing codes being fails

* add test case

* change plugin_id field type from text to uuid

* make plugin_id to be foreign key

* fix merge issue

* fix cassandra script

(cherry picked from commit 8c4e5c6)
bungle pushed a commit that referenced this pull request May 8, 2023
…ance to be exchanged for an access token by a different plugin instance (#10011)

* fix(oauth2): prevent an authorization code created by one plugin instance to be exchanged for an access token by a different plugin instance

* verify only if plugin_id is present to avoid existing codes being fails

* add test case

* change plugin_id field type from text to uuid

* make plugin_id to be foreign key

* fix merge issue

* fix cassandra script

(cherry picked from commit 8c4e5c6)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants