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

OAuth2: Add samesite attribute support for all OAuth2 supported cookie types #37917

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b5f1d51
initial
Yueren-Wang Jan 8, 2025
8c98839
fix
Yueren-Wang Jan 8, 2025
39c8e57
fixunittest
Yueren-Wang Jan 8, 2025
9d264ad
addunittests
Yueren-Wang Jan 9, 2025
c0e91d6
formatfix
Yueren-Wang Jan 9, 2025
3caf535
comments
Yueren-Wang Jan 9, 2025
4cb4b26
moreformatfix
Yueren-Wang Jan 9, 2025
5f0d302
moreformat
Yueren-Wang Jan 9, 2025
7510ec7
changelog
Yueren-Wang Jan 9, 2025
9d5c629
Merge branch 'main' into yueren-wang-samesite-cookie-support
Yueren-Wang Jan 9, 2025
7f7a55a
addmoreunittests
Yueren-Wang Jan 9, 2025
b8ba987
adjustcoverage
Yueren-Wang Jan 9, 2025
fd48512
adjustcoverage
Yueren-Wang Jan 9, 2025
8acb647
Merge branch 'yueren-wang-samesite-cookie-support' of github.com:Yuer…
Yueren-Wang Jan 9, 2025
c8ef936
network: More efficient caching for Envoy socket addresses (#37832)
abeyad Jan 6, 2025
6181bbf
initial
Yueren-Wang Jan 8, 2025
28eb6b3
fix
Yueren-Wang Jan 8, 2025
733a359
fixunittest
Yueren-Wang Jan 8, 2025
2e9f605
addunittests
Yueren-Wang Jan 9, 2025
08f8efb
formatfix
Yueren-Wang Jan 9, 2025
c3501de
comments
Yueren-Wang Jan 9, 2025
4103b4d
moreformatfix
Yueren-Wang Jan 9, 2025
b8ffcab
moreformat
Yueren-Wang Jan 9, 2025
deec245
changelog
Yueren-Wang Jan 9, 2025
1c12a63
fix hcm fuzz test case (#37901)
antoniovleonti Jan 6, 2025
fb4336a
Revert "Revert "dynamic_modules: adds dynamic metadata callbakcs" (#3…
mathetake Jan 7, 2025
b2e069f
docs: reword the example in the aggregate cluster docs (#37872)
agrawroh Jan 7, 2025
099655a
Update QUICHE from 71111c723 to e599ad25d (#37895)
alyssawilk Jan 7, 2025
5e73dbd
network: Clarify comments on received addresses cache (#37902)
abeyad Jan 7, 2025
2419296
deps: Bump `bazel_gazelle` -> 0.39.1 (#36638)
dependency-envoy[bot] Jan 7, 2025
847389f
orca: refactored the lbPolicyData and orca report callbacks (#37556)
wbpcode Jan 7, 2025
fe87c87
Reapply "rbac: add delay_deny implementation in RBAC network filter (…
yangminzhu Jan 7, 2025
ecfbf5a
deps: Update `proxy_wasm_cpp_host` -> c4d7bb0, `wasmtime` -> 24.0.2, …
leonm1 Jan 7, 2025
2548fb6
deps: Bump `rules_foreign_cc` -> 0.13.0 (#37569)
dependency-envoy[bot] Jan 7, 2025
0284949
backtrace: Add missing includes for std::cerr and std::ostream (#37897)
derekmauro Jan 7, 2025
121ad8a
[mobile] Report network subtype when on cellular (#37903)
RenjieTang Jan 7, 2025
03a4ca7
tools/notifier: making it clear when the ical code isn't working (#37…
alyssawilk Jan 7, 2025
8b2469e
Add truncate to capabilities of AsyncFileHandle (#37870)
ravenblackx Jan 7, 2025
6709dbc
dynamic_modules: don't call on_http_filter_destroy_ if filter is null…
bplotnick Jan 8, 2025
e59db87
support backoff between retries in udp_proxy (#37912)
IssaAbuKalbein Jan 8, 2025
f256b34
ext_proc: change default sampling to false inheriting from parent dec…
cainelli Jan 8, 2025
1075240
mobile: Move --noincompatible_enable_android_toolchain_resolution to …
fredyw Jan 8, 2025
ff04295
deps: Bump `envoy_examples` -> 0.0.10 (#37935)
dependency-envoy[bot] Jan 8, 2025
9b8f7e1
dynamic_modules: adds send response (#37856)
bplotnick Jan 9, 2025
d799842
http2: propagates reset events to CodecEventCallbacks when sending RS…
birenroy Jan 9, 2025
6e5b2bf
lua: add new function to get the name of the matched route (#37877)
agrawroh Jan 9, 2025
4872ec7
changelogs: Release cleanups (#37934)
phlax Jan 9, 2025
60b798e
Fix the crash caused by continuing the filter after recreating stream…
johnlanni Jan 9, 2025
18b1f19
upstream lb: optimize the upstream load balancer (#37576)
wbpcode Jan 9, 2025
51c7142
docs: fix notes in AWS Credentials filter docs (#37938)
agrawroh Jan 9, 2025
cd6aa74
grpc: removing exceptions from client creation (#37765)
alyssawilk Jan 9, 2025
95749c8
addmoreunittests
Yueren-Wang Jan 9, 2025
4bff54e
adjustcoverage
Yueren-Wang Jan 9, 2025
8ac753f
Merge branch 'yueren-wang-samesite-cookie-support' of github.com:Yuer…
Yueren-Wang Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion api/envoy/extensions/filters/http/oauth2/v3/oauth.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,54 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#extension: envoy.filters.http.oauth2]
//

// [#next-free-field: 3]
message CookieConfig {
enum CookieType {
UNSPECIFIED = 0;
BEARER_TOKEN = 1;
OAUTH_HMAC = 2;
OAUTH_EXPIRES = 3;
ID_TOKEN = 4;
REFRESH_TOKEN = 5;
OAUTH_NONCE = 6;
}

enum SameSite {
DISABLED = 0;
STRICT = 1;
LAX = 2;
NONE = 3;
}

// The type of cookie. Must be specified.
CookieType type = 1;

// The value used for the SameSite cookie attribute. If not specified,
// defaults to DISABLED which means SameSite value will be missing.
SameSite same_site = 2;
}

// [#next-free-field: 7]
message CookieConfigs {
// Configuration for bearer token cookie
CookieConfig bearer_token_cookie_config = 1;

// Configuration for OAuth HMAC cookie
CookieConfig oauth_hmac_cookie_config = 2;

// Configuration for OAuth expires cookie
CookieConfig oauth_expires_cookie_config = 3;

// Configuration for ID token cookie
CookieConfig id_token_cookie_config = 4;

// Configuration for refresh token cookie
CookieConfig refresh_token_cookie_config = 5;

// Configuration for OAuth nonce cookie
CookieConfig oauth_nonce_cookie_config = 6;
}

// [#next-free-field: 6]
message OAuth2Credentials {
// [#next-free-field: 7]
Expand Down Expand Up @@ -84,7 +132,7 @@ message OAuth2Credentials {

// OAuth config
//
// [#next-free-field: 21]
// [#next-free-field: 22]
message OAuth2Config {
enum AuthType {
// The ``client_id`` and ``client_secret`` will be sent in the URL encoded request body.
Expand Down Expand Up @@ -186,6 +234,9 @@ message OAuth2Config {
// will still process incoming Refresh Tokens as part of the HMAC if they are there. This is to ensure compatibility while switching this setting on. Future
// sessions would not set the Refresh Token cookie header.
bool disable_refresh_token_set_cookie = 20;

// Controls for attributes that can be set on the cookies.
CookieConfigs cookie_configs = 21;
}

// Filter config.
Expand Down
8 changes: 8 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,14 @@ new_features:
change: |
Added :ref:`routeName() <config_http_filters_lua_stream_info_route_name>` API to the Stream Info Object to get the
name of the route matched by the filter chain.
- area: oauth2
change: |
Add the option to specify samesite cookie attribute value for oauth2 supported cookies.
To specify samesite attribute, choose one of the value from ``strict``,``lax`` or ``none``. if not specified,
a default value of ``disabled`` will be assigned and there will be no samesite value in cookie. see
:ref:`apply_on_stream_done <envoy_v3_api_field_extensions.filters.http.oauth2.v3.CookieConfigs>` and
:ref:`apply_on_stream_done <envoy_v3_api_field_extensions.filters.http.oauth2.v3.CookieConfig>
for more details.

deprecated:
- area: rbac
Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/http/oauth2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ envoy_cc_library(
name = "oauth_lib",
srcs = ["filter.cc"],
hdrs = ["filter.h"],
copts = ["-std=c++17"],
deps = [
":oauth_client",
"//envoy/server:filter_config_interface",
Expand Down
146 changes: 113 additions & 33 deletions source/extensions/filters/http/oauth2/filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Http::RegisterCustomInlineHeader<Http::CustomInlineHeaderRegistry::Type::Request

constexpr const char* CookieDeleteFormatString =
"{}=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
constexpr const char* CookieTailHttpOnlyFormatString = ";path=/;Max-Age={};secure;HttpOnly";
constexpr const char* CookieTailHttpOnlyFormatString = ";path=/;Max-Age={};secure;HttpOnly{}";
constexpr const char* CookieDomainFormatString = ";domain={}";

constexpr absl::string_view UnauthorizedBodyMessage = "OAuth flow failed.";
Expand All @@ -60,6 +60,9 @@ constexpr absl::string_view REDIRECT_FOR_CREDENTIALS = "oauth.missing_credential
constexpr absl::string_view SIGN_OUT = "oauth.sign_out";
constexpr absl::string_view DEFAULT_AUTH_SCOPE = "user";

constexpr absl::string_view SameSiteLax = ";SameSite=Lax";
constexpr absl::string_view SameSiteStrict = ";SameSite=Strict";
constexpr absl::string_view SameSiteNone = ";SameSite=None";
constexpr absl::string_view HmacPayloadSeparator = "\n";

template <class T>
Expand Down Expand Up @@ -129,6 +132,28 @@ getAuthType(envoy::extensions::filters::http::oauth2::v3::OAuth2Config_AuthType
}
}

// Helper function to get SameSite attribute string from proto enum.
std::string
getSameSiteString(envoy::extensions::filters::http::oauth2::v3::CookieConfig_SameSite same_site) {
switch (same_site) {
PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
case envoy::extensions::filters::http::oauth2::v3::CookieConfig_SameSite::
CookieConfig_SameSite_STRICT:
return std::string(SameSiteStrict);
case envoy::extensions::filters::http::oauth2::v3::CookieConfig_SameSite::
CookieConfig_SameSite_LAX:
return std::string(SameSiteLax);
case envoy::extensions::filters::http::oauth2::v3::CookieConfig_SameSite::
CookieConfig_SameSite_NONE:
return std::string(SameSiteNone);
case envoy::extensions::filters::http::oauth2::v3::CookieConfig_SameSite::
CookieConfig_SameSite_DISABLED:
return EMPTY_STRING;
default:
return EMPTY_STRING;
}
}

Http::Utility::QueryParamsMulti buildAutorizationQueryParams(
const envoy::extensions::filters::http::oauth2::v3::OAuth2Config& proto_config) {
auto query_params =
Expand Down Expand Up @@ -246,7 +271,37 @@ FilterConfig::FilterConfig(
use_refresh_token_(FilterConfig::shouldUseRefreshToken(proto_config)),
disable_id_token_set_cookie_(proto_config.disable_id_token_set_cookie()),
disable_access_token_set_cookie_(proto_config.disable_access_token_set_cookie()),
disable_refresh_token_set_cookie_(proto_config.disable_refresh_token_set_cookie()) {
disable_refresh_token_set_cookie_(proto_config.disable_refresh_token_set_cookie()),
bearer_token_cookie_settings_(
(proto_config.has_cookie_configs() &&
proto_config.cookie_configs().has_bearer_token_cookie_config())
? CookieSettings(proto_config.cookie_configs().bearer_token_cookie_config())
: CookieSettings()),
hmac_cookie_settings_(
(proto_config.has_cookie_configs() &&
proto_config.cookie_configs().has_oauth_hmac_cookie_config())
? CookieSettings(proto_config.cookie_configs().oauth_hmac_cookie_config())
: CookieSettings()),
expires_cookie_settings_(
(proto_config.has_cookie_configs() &&
proto_config.cookie_configs().has_oauth_expires_cookie_config())
? CookieSettings(proto_config.cookie_configs().oauth_expires_cookie_config())
: CookieSettings()),
id_token_cookie_settings_(
(proto_config.has_cookie_configs() &&
proto_config.cookie_configs().has_id_token_cookie_config())
? CookieSettings(proto_config.cookie_configs().id_token_cookie_config())
: CookieSettings()),
refresh_token_cookie_settings_(
(proto_config.has_cookie_configs() &&
proto_config.cookie_configs().has_refresh_token_cookie_config())
? CookieSettings(proto_config.cookie_configs().refresh_token_cookie_config())
: CookieSettings()),
nonce_cookie_settings_(
(proto_config.has_cookie_configs() &&
proto_config.cookie_configs().has_oauth_nonce_cookie_config())
? CookieSettings(proto_config.cookie_configs().oauth_nonce_cookie_config())
: CookieSettings()) {
if (!context.clusterManager().clusters().hasCluster(oauth_token_endpoint_.cluster())) {
throw EnvoyException(fmt::format("OAuth2 filter: unknown cluster '{}' in config. Please "
"specify which cluster to direct OAuth requests to.",
Expand Down Expand Up @@ -544,8 +599,10 @@ void OAuth2Filter::redirectToOAuthServer(Http::RequestHeaderMap& headers) {
if (!csrf_token_cookie_exists) {
// Expire the CSRF token cookie in 10 minutes.
// This should be enough time for the user to complete the OAuth flow.
std::string expire_in = std::to_string(10 * 60);
std::string cookie_tail_http_only = fmt::format(CookieTailHttpOnlyFormatString, expire_in);
std::string csrf_expires = std::to_string(10 * 60);
std::string same_site = getSameSiteString(config_->nonceCookieSettings().same_site_);
std::string cookie_tail_http_only =
fmt::format(CookieTailHttpOnlyFormatString, csrf_expires, same_site);
if (!config_->cookieDomain().empty()) {
cookie_tail_http_only = absl::StrCat(
fmt::format(CookieDomainFormatString, config_->cookieDomain()), cookie_tail_http_only);
Expand Down Expand Up @@ -728,6 +785,47 @@ std::string OAuth2Filter::getExpiresTimeForIdToken(const std::string& id_token,
return std::to_string(expires_in.count());
}

// Helper function to build the cookie tail string.
std::string OAuth2Filter::BuildCookieTail(int cookie_type) const {
std::string same_site;
std::string expires_time = expires_in_;

switch (cookie_type) {
PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
case 1: // BEARER_TOKEN TYPE
same_site = getSameSiteString(config_->bearerTokenCookieSettings().same_site_);
break;
case 2: // OAUTH_HMAC TYPE
same_site = getSameSiteString(config_->hmacCookieSettings().same_site_);
break;
case 3: // OAUTH_EXPIRES TYPE
same_site = getSameSiteString(config_->expiresCookieSettings().same_site_);
break;
case 4: // ID_TOKEN TYPE
same_site = getSameSiteString(config_->idTokenCookieSettings().same_site_);
expires_time = expires_id_token_in_;
break;
case 5: // REFRESH_TOKEN TYPE
same_site = getSameSiteString(config_->refreshTokenCookieSettings().same_site_);
expires_time = expires_refresh_token_in_;
break;
case 6: // OAUTH_NONCE TYPE
same_site = getSameSiteString(config_->refreshTokenCookieSettings().same_site_);
break;
default:
ENVOY_LOG(debug,
"The CookieType is not supported. It must be set to one of the supported types");
break;
}

std::string cookie_tail = fmt::format(CookieTailHttpOnlyFormatString, expires_time, same_site);
if (!config_->cookieDomain().empty()) {
cookie_tail =
absl::StrCat(fmt::format(CookieDomainFormatString, config_->cookieDomain()), cookie_tail);
}
return cookie_tail;
}

void OAuth2Filter::onGetAccessTokenSuccess(const std::string& access_code,
const std::string& id_token,
const std::string& refresh_token,
Expand Down Expand Up @@ -809,51 +907,33 @@ void OAuth2Filter::onRefreshAccessTokenFailure() {
void OAuth2Filter::addResponseCookies(Http::ResponseHeaderMap& headers,
const std::string& encoded_token) const {
// We use HTTP Only cookies.
std::string cookie_tail_http_only = fmt::format(CookieTailHttpOnlyFormatString, expires_in_);
if (!config_->cookieDomain().empty()) {
cookie_tail_http_only = absl::StrCat(
fmt::format(CookieDomainFormatString, config_->cookieDomain()), cookie_tail_http_only);
}

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

// Set the cookies in the response headers.
headers.addReferenceKey(
Http::Headers::get().SetCookie,
absl::StrCat(cookie_names.oauth_hmac_, "=", encoded_token, cookie_tail_http_only));
headers.addReferenceKey(
Http::Headers::get().SetCookie,
absl::StrCat(cookie_names.oauth_expires_, "=", new_expires_, cookie_tail_http_only));
absl::StrCat(cookie_names.oauth_hmac_, "=", encoded_token, BuildCookieTail(2))); // OAUTH_HMAC

headers.addReferenceKey(Http::Headers::get().SetCookie,
absl::StrCat(cookie_names.oauth_expires_, "=", new_expires_,
BuildCookieTail(3))); // OAUTH_EXPIRES

if (!access_token_.empty()) {
headers.addReferenceKey(
Http::Headers::get().SetCookie,
absl::StrCat(cookie_names.bearer_token_, "=", access_token_, cookie_tail_http_only));
headers.addReferenceKey(Http::Headers::get().SetCookie,
absl::StrCat(cookie_names.bearer_token_, "=", access_token_,
BuildCookieTail(1))); // BEARER_TOKEN
}

if (!id_token_.empty()) {
std::string id_token_cookie_tail_http_only =
fmt::format(CookieTailHttpOnlyFormatString, expires_id_token_in_);
if (!config_->cookieDomain().empty()) {
id_token_cookie_tail_http_only =
absl::StrCat(fmt::format(CookieDomainFormatString, config_->cookieDomain()),
id_token_cookie_tail_http_only);
}
headers.addReferenceKey(
Http::Headers::get().SetCookie,
absl::StrCat(cookie_names.id_token_, "=", id_token_, id_token_cookie_tail_http_only));
absl::StrCat(cookie_names.id_token_, "=", id_token_, BuildCookieTail(4))); // ID_TOKEN
}

if (!refresh_token_.empty()) {
std::string refresh_token_cookie_tail_http_only =
fmt::format(CookieTailHttpOnlyFormatString, expires_refresh_token_in_);
if (!config_->cookieDomain().empty()) {
refresh_token_cookie_tail_http_only =
absl::StrCat(fmt::format(CookieDomainFormatString, config_->cookieDomain()),
refresh_token_cookie_tail_http_only);
}
headers.addReferenceKey(Http::Headers::get().SetCookie,
absl::StrCat(cookie_names.refresh_token_, "=", refresh_token_,
refresh_token_cookie_tail_http_only));
BuildCookieTail(5))); // REFRESH_TOKEN
}
}

Expand Down
29 changes: 28 additions & 1 deletion source/extensions/filters/http/oauth2/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,26 @@ class FilterConfig {
}
bool shouldUseRefreshToken(
const envoy::extensions::filters::http::oauth2::v3::OAuth2Config& proto_config) const;
struct CookieSettings {
CookieSettings(const envoy::extensions::filters::http::oauth2::v3::CookieConfig& config)
: same_site_(config.same_site()) {}

// Default constructor
CookieSettings()
: same_site_(envoy::extensions::filters::http::oauth2::v3::CookieConfig_SameSite::
CookieConfig_SameSite_DISABLED) {}

const envoy::extensions::filters::http::oauth2::v3::CookieConfig_SameSite same_site_;
};

const CookieSettings& bearerTokenCookieSettings() const { return bearer_token_cookie_settings_; }
const CookieSettings& hmacCookieSettings() const { return hmac_cookie_settings_; }
const CookieSettings& expiresCookieSettings() const { return expires_cookie_settings_; }
const CookieSettings& idTokenCookieSettings() const { return id_token_cookie_settings_; }
const CookieSettings& refreshTokenCookieSettings() const {
return refresh_token_cookie_settings_;
}
const CookieSettings& nonceCookieSettings() const { return nonce_cookie_settings_; }

private:
static FilterStats generateStats(const std::string& prefix, Stats::Scope& scope);
Expand Down Expand Up @@ -199,6 +219,12 @@ class FilterConfig {
const bool disable_access_token_set_cookie_ : 1;
const bool disable_refresh_token_set_cookie_ : 1;
absl::optional<RouteRetryPolicy> retry_policy_;
const CookieSettings bearer_token_cookie_settings_;
const CookieSettings hmac_cookie_settings_;
const CookieSettings expires_cookie_settings_;
const CookieSettings id_token_cookie_settings_;
const CookieSettings refresh_token_cookie_settings_;
const CookieSettings nonce_cookie_settings_;
};

using FilterConfigSharedPtr = std::shared_ptr<FilterConfig>;
Expand Down Expand Up @@ -268,7 +294,7 @@ class OAuth2Filter : public Http::PassThroughFilter,
Logger::Loggable<Logger::Id::oauth2> {
public:
OAuth2Filter(FilterConfigSharedPtr config, std::unique_ptr<OAuth2Client>&& oauth_client,
TimeSource& time_source, Random::RandomGenerator& random);
TimeSource& time_source, Random::RandomGenerator& randomhmacCookieSettings);

// Http::PassThroughFilter
Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override;
Expand Down Expand Up @@ -331,6 +357,7 @@ class OAuth2Filter : public Http::PassThroughFilter,
const std::chrono::seconds& expires_in) const;
std::string getExpiresTimeForIdToken(const std::string& id_token,
const std::chrono::seconds& expires_in) const;
std::string BuildCookieTail(int cookie_type) const;
void addResponseCookies(Http::ResponseHeaderMap& headers, const std::string& encoded_token) const;
const std::string& bearerPrefix() const;
CallbackValidationResult validateOAuthCallback(const Http::RequestHeaderMap& headers,
Expand Down
Loading