Skip to content

Commit

Permalink
oauth2: fix Max-Age attribute of Set-Cookie response header (#26764)
Browse files Browse the repository at this point in the history
Signed-off-by: Gustavo Gabriel Moyano <gustavo.g.moyano@gmail.com>
  • Loading branch information
ggmoy authored May 6, 2023
1 parent 5523257 commit f88e296
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 3 deletions.
7 changes: 7 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ minor_behavior_changes:
bug_fixes:
# *Changes expected to improve the state of the world and are unlikely to have negative effects*
- area: oauth2
change: |
The Max-Age attribute of Set-Cookie HTTP response header was being assigned a value representing Seconds Since
the Epoch, causing cookies to expire in ~53 years. This was fixed an now it is being assinged a value representing
the number of seconds until the cookie expires.
This behavioral change can be temporarily reverted by setting runtime guard
``envoy.reloadable_features.oauth_use_standard_max_age_value`` to false.
- area: tls
change: |
Fix build FIPS compliance when using both FIPS mode and Wasm extensions (``--define boringssl=fips`` and ``--define wasm=v8``).
Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ RUNTIME_GUARD(envoy_reloadable_features_initialize_upstream_filters);
RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name);
RUNTIME_GUARD(envoy_reloadable_features_no_full_scan_certs_on_sni_mismatch);
RUNTIME_GUARD(envoy_reloadable_features_oauth_header_passthrough_fix);
RUNTIME_GUARD(envoy_reloadable_features_oauth_use_standard_max_age_value);
RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding);
RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout);
RUNTIME_GUARD(envoy_reloadable_features_prohibit_route_refresh_after_response_headers_sent);
Expand Down
14 changes: 11 additions & 3 deletions source/extensions/filters/http/oauth2/filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ void OAuth2Filter::updateTokens(const std::string& access_token, const std::stri
access_token_ = access_token;
id_token_ = id_token;
refresh_token_ = refresh_token;
expires_in_ = std::to_string(expires_in.count());

const auto new_epoch = time_source_.systemTime() + expires_in;
new_expires_ = std::to_string(
Expand Down Expand Up @@ -510,10 +511,17 @@ void OAuth2Filter::finishGetAccessTokenFlow() {

void OAuth2Filter::addResponseCookies(Http::ResponseHeaderMap& headers,
const std::string& encoded_token) const {
std::string max_age;
if (Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.oauth_use_standard_max_age_value")) {
max_age = expires_in_;
} else {
max_age = new_expires_;
}

// We use HTTP Only cookies for the HMAC and Expiry.
const std::string cookie_tail = fmt::format(CookieTailFormatString, new_expires_);
const std::string cookie_tail_http_only =
fmt::format(CookieTailHttpOnlyFormatString, new_expires_);
const std::string cookie_tail = fmt::format(CookieTailFormatString, max_age);
const std::string cookie_tail_http_only = fmt::format(CookieTailHttpOnlyFormatString, max_age);

const CookieNames& cookie_names = config_->cookieNames();

Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/http/oauth2/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ class OAuth2Filter : public Http::PassThroughDecoderFilter, public FilterCallbac
std::string access_token_; // TODO - see if we can avoid this being a member variable
std::string id_token_;
std::string refresh_token_;
std::string expires_in_;
std::string new_expires_;
absl::string_view host_;
std::string state_;
Expand Down
33 changes: 33 additions & 0 deletions test/extensions/filters/http/oauth2/filter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,39 @@ TEST_F(OAuth2Test, OAuthTestFullFlowPostWithParameters) {
*/
TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokens) {

// Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs.
test_time_.setSystemTime(SystemTime(std::chrono::seconds(1000)));

// Expected response after the callback is complete.
Http::TestRequestHeaderMapImpl expected_headers{
{Http::Headers::get().Status.get(), "302"},
{Http::Headers::get().SetCookie.get(),
"OauthHMAC="
"MjI2YmI5YTRiZjJlNTFlNDUzZWVjOWUzYmU1MThlNGQyNDgyNzA0ZTBkMGQyY2M3M2QyMzg3NTRkZTY0YmU5YQ==;"
"version=1;path=/;Max-Age=600;secure;HttpOnly"},
{Http::Headers::get().SetCookie.get(),
"OauthExpires=1600;version=1;path=/;Max-Age=600;secure;HttpOnly"},
{Http::Headers::get().SetCookie.get(),
"BearerToken=access_code;version=1;path=/;Max-Age=600;secure"},
{Http::Headers::get().SetCookie.get(),
"IdToken=some-id-token;version=1;path=/;Max-Age=600;secure"},
{Http::Headers::get().SetCookie.get(),
"RefreshToken=some-refresh-token;version=1;path=/;Max-Age=600;secure"},
{Http::Headers::get().Location.get(), ""},
};

EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), true));

filter_->onGetAccessTokenSuccess("access_code", "some-id-token", "some-refresh-token",
std::chrono::seconds(600));
}

TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokens_oauth_use_standard_max_age_value) {
TestScopedRuntime scoped_runtime;
scoped_runtime.mergeValues({
{"envoy.reloadable_features.oauth_use_standard_max_age_value", "false"},
});

// Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs.
test_time_.setSystemTime(SystemTime(std::chrono::seconds(0)));

Expand Down

0 comments on commit f88e296

Please sign in to comment.