Skip to content

fix oauth filter sds update#16253

Merged
snowp merged 13 commits intoenvoyproxy:mainfrom
Inode1:oauth-sds-fix
May 14, 2021
Merged

fix oauth filter sds update#16253
snowp merged 13 commits intoenvoyproxy:mainfrom
Inode1:oauth-sds-fix

Conversation

@Inode1
Copy link
Copy Markdown
Contributor

@Inode1 Inode1 commented Apr 30, 2021

Additional Description:
The client_secret field was not updated on configuration changes.

Risk Level: Low
Testing: n/a
Docs Changes: n/a
Release Notes: inline
Fixes #16126

@repokitteh-read-only
Copy link
Copy Markdown

Hi @Inode1, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #16253 was opened by Inode1.

see: more, trace.

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
private:
void readAndWatchSecret(std::string& value,
Secret::GenericSecretConfigProvider& secret_provider) {
void readAndWatchSecret(std::string& value, Secret::GenericSecretConfigProvider& secret_provider,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you return the update_callback from the method instead of using a parameter? I think it would be more readable...

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.

Good point.

@dmitri-d
Copy link
Copy Markdown
Contributor

Could you provide a brief explanation of how this fixes the problem in #16126? Also, could you add tests?

@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented Apr 30, 2021

Yup. This method secret_provider->addUpdateCallback calls this at the end. And if we don't save results in unique_ptr it will be destroyed and deleted from the callbacks list according to this. We need to create two objects here. If we don't do it the update_callback_ will be rewrited and callback will be deleted.

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
@dmitri-d
Copy link
Copy Markdown
Contributor

Nice catch, @Inode1. Could you add a test?

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented May 2, 2021

Here you are, check this out.

@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented May 3, 2021

/retest

@repokitteh-read-only
Copy link
Copy Markdown

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #16253 (comment) was created by @Inode1.

see: more, trace.


token_callback->onConfigUpdate(decoded_resources_token.refvec_, "");
EXPECT_EQ(secret_reader.clientSecret(), "client_test");
EXPECT_EQ(secret_reader.tokenSecret(), "token_test");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you add another check where token stays the same, but the client secret is updated again? To make sure that an update to client secret doesn't overwrite the token.

@dmitri-d
Copy link
Copy Markdown
Contributor

dmitri-d commented May 3, 2021

Lgtm, just a small ask in the test.

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
Copy link
Copy Markdown
Contributor

@snowp snowp left a comment

Choose a reason for hiding this comment

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

Thanks! Could you include a bit more information in the PR description that describes that the bug is and how this fixes it?

Also, do you think it would be possible to write an integration test for this? Would be good to have a test that verifies this end to end.

Event::SimulatedTimeSystem test_time_;
};

// Verifies that the OAuth SDSSecretReader correctly updates dynamic generic secret
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: end in period to keep comments using proper grammar

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.

Done

)EOF";

envoy::extensions::transport_sockets::tls::v3::Secret typed_secret;
TestUtility::loadFromYaml(TestEnvironment::substitute(yaml_client), typed_secret);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think you need to use substitute here and elsewhere since the input doesn't use any of the substitution formats.

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.

Done

TEST_F(OAuth2Test, SdsDynamicGenericSecret) {
NiceMock<Server::MockConfigTracker> config_tracker;
std::unique_ptr<Secret::SecretManager> secret_manager(
new Secret::SecretManagerImpl(config_tracker));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can probably get by here by allocating this on the stack?

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.

Done

NiceMock<Server::Configuration::MockTransportSocketFactoryContext> secret_context;
NiceMock<LocalInfo::MockLocalInfo> local_info;
Api::ApiPtr api = Api::createApiForTest();
Stats::IsolatedStoreImpl stats;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

At least this one can use the store_ defined on the fixture class instead of redefining one here

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.

I think it will be more readable if we leave it in the function scope to save the context.

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented May 4, 2021

I don't know how to update secrets in integration tests, so if you give me a hint, I think I can do it.

@Inode1 Inode1 requested a review from snowp May 4, 2021 12:37
@snowp
Copy link
Copy Markdown
Contributor

snowp commented May 6, 2021

Maybe try looking at sds_static_integration_test or sds_dynamic_integration_test for how that sets up SDS triggers?

i.makarychev added 3 commits May 7, 2021 14:19
Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented May 8, 2021

/retest

@repokitteh-read-only
Copy link
Copy Markdown

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #16253 (comment) was created by @Inode1.

see: more, trace.

i.makarychev added 2 commits May 8, 2021 19:23
Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented May 9, 2021

@snowp could you review the test and could you check this and this I think it's not thread safe to use it this way.

Copy link
Copy Markdown
Contributor

@snowp snowp left a comment

Choose a reason for hiding this comment

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

Thanks for getting an integration test together!

Re your question on thread safety it's hard to say without deeper inspection. What specifically makes you think that it's not thread safe? At a quick look it does seem problematic that we're just updating the secrets from the main thread and then presumably read from the filters? We might want to make use of a TLS slot here to avoid that, but if we can get an integration test reproducing the issue first that would be awesome.

EXPECT_EQ("302", response->headers().getStatusValue());
}

std::string parseSetCookieValue(const Http::HeaderMap& headers, const std::string& key) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you use the helper in http/utlity.h instead of redefining this here?

initialize();

Filesystem::WatcherPtr watcher = dispatcher_->createFilesystemWatcher();
Filesystem::Watcher::OnChangedCb on_changed_cb_;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not used?

TEST_F(OauthIntegrationTest, AuthenticationFlow) {
initialize();

Filesystem::WatcherPtr watcher = dispatcher_->createFilesystemWatcher();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe extract this stuff into a setup function? Just to declutter the test file

Also: does it make sense to do one authentication flow, reload the secrets, then do another one? That way we're verifying a real world secret rotation scenario

Comment on lines +190 to +204
watcher->addWatch(TestEnvironment::temporaryPath("token_secret.yaml"),
Filesystem::Watcher::Events::MovedTo,
[&](uint32_t) -> void { dispatcher_->exit(); });

watcher->addWatch(TestEnvironment::temporaryPath("hmac_secret.yaml"),
Filesystem::Watcher::Events::MovedTo,
[&](uint32_t) -> void { dispatcher_->exit(); });

TestEnvironment::renameFile(TestEnvironment::temporaryPath("token_secret_1.yaml"),
TestEnvironment::temporaryPath("token_secret.yaml"));
dispatcher_->run(Event::Dispatcher::RunType::Block);
TestEnvironment::renameFile(TestEnvironment::temporaryPath("hmac_secret_1.yaml"),
TestEnvironment::temporaryPath("hmac_secret.yaml"));

dispatcher_->run(Event::Dispatcher::RunType::Block);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It might be simpler to wait for a stat to update that reflects the secret reload, assuming we have one?

{"x-forwarded-proto", "http"},
{":authority", "authority"},
{"authority", "Bearer token"}};
absl::string_view host_ = headers.Host()->value().getStringView();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The _ suffix is reserved for field members, so I'd call this host instead

Comment on lines +251 to +257
const auto hmac_payload = absl::StrCat(host_, expires_, token_);
std::string hmac_secret = "hmac_secret_1";
std::vector<uint8_t> hmac_secret_vec(hmac_secret.begin(), hmac_secret.end());
const auto pre_encoded_hmac =
Hex::encode(crypto_util.getSha256Hmac(hmac_secret_vec, hmac_payload));
std::string encoded_hmac;
absl::Base64Escape(pre_encoded_hmac, &encoded_hmac);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Putting this in a helper function might help readability, its quite a bit of code just to verify the hmac

Comment on lines +247 to +249
std::string expires_ = parseSetCookieValue(headers_, "OauthExpires");
std::string token_ = parseSetCookieValue(headers_, "BearerToken");
std::string hmac_ = parseSetCookieValue(headers_, "OauthHMAC");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remove the _ suffix since these aren't field variables

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented May 11, 2021

Thank you, @snowp your remarks were taken into consideration. So I will recheck my suspicions about thread safe and if this is confirmed I will open a new issue.

@Inode1
Copy link
Copy Markdown
Contributor Author

Inode1 commented May 12, 2021

/retest

@repokitteh-read-only
Copy link
Copy Markdown

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #16253 (comment) was created by @Inode1.

see: more, trace.

Copy link
Copy Markdown
Contributor

@snowp snowp left a comment

Choose a reason for hiding this comment

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

Thanks this looks great, just a few more comments and then I think we're good to go

* @param key the key for the particular set-cookie value to return
* @return std::string the parsed set-cookie value, or "" if none exists
**/
std::string parseSetCookieValue(const HeaderMap& headers, const std::string& key);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mind adding a test for this function? Ideally we have coverage for this outside of extensions. Thanks!

Copy link
Copy Markdown
Contributor Author

@Inode1 Inode1 May 12, 2021

Choose a reason for hiding this comment

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

I can copy-past some tests from utility_test.cpp with parseCookieValue check and trasnform them for parseSetCookieValue. Will it be okay?


OAuth2CookieValidator validator{api_->timeSource()};
validator.setParams(validate_headers, std::string(hmac_secret));
ASSERT_TRUE(validator.isValid());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if you return validator.isValid() here instead you can do an EXPECT_TRUE at call site and provide better error messaging

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
Copy link
Copy Markdown
Contributor

@snowp snowp left a comment

Choose a reason for hiding this comment

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

Thanks!

@snowp snowp merged commit 1f4ca4c into envoyproxy:main May 14, 2021
jamesmulcahy pushed a commit to jamesmulcahy/envoy that referenced this pull request Aug 13, 2021
The client_secret field was not updated on configuration changes.

Signed-off-by: i.makarychev <i.makarychev@tinkoff.ru>
Signed-off-by: James Mulcahy <jmulcahy@netflix.com>
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.

envoy.filters.http.oauth2 stops working after upgrade from 1.17.2 to 1.18.2

3 participants