From 2e64d21c3051bbb3e9f402dc7cb51d096d152899 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Fri, 20 Mar 2020 10:43:24 -0700 Subject: [PATCH] Support sending requests through a web proxy - This commit does not yet include support for proxy usernames and passwords Signed-off-by: Margo Crawford Signed-off-by: Andrew Chang --- config/oidc/config.proto | 16 ++++++ src/common/http/http.cc | 64 +++++++++++++++++++---- src/common/http/http.h | 34 +++++++------ src/config/get_config.cc | 19 +++++-- src/filters/oidc/oidc_filter.cc | 4 +- test/common/http/http_test.cc | 23 ++++++++- test/common/http/mocks.h | 3 +- test/config/getconfig_test.cc | 73 +++++++++++++++++++-------- test/filters/oidc/oidc_filter_test.cc | 17 ++++--- test/fixtures/valid-config.json | 1 + 10 files changed, 191 insertions(+), 63 deletions(-) diff --git a/config/oidc/config.proto b/config/oidc/config.proto index 3a0be151..bc7aee25 100644 --- a/config/oidc/config.proto +++ b/config/oidc/config.proto @@ -142,4 +142,20 @@ message OIDCConfig { // the Token Endpoint of the OIDC Identity Provider. // Optional. string trusted_certificate_authority = 14; + + // The Authservice makes two kinds of direct network connections directly to the OIDC Provider. + // Both are POST requests to the configured `token_uri` of the OIDC Provider. + // The first is to exchange the authorization code for tokens, and the other is to use the + // refresh token to obtain new tokens. Configure the `proxy_uri` when + // both of these requests should be made through a web proxy. The format of `proxy_uri` is + // `http://proxyserver.example.com:8080`, where `:` is optional. + // Userinfo (usernames and passwords) in the `proxy_uri` setting are not yet supported. + // The `proxy_uri` should always start with `http://`. + // The Authservice will upgrade the connection to the OIDC provider to HTTPS using + // an HTTP CONNECT request to the proxy server. The proxy server will see the hostname and port number + // of the OIDC provider in plain text in the CONNECT request, but all other communication will occur + // over an encrypted HTTPS connection negotiated directly between the Authservice and + // the OIDC provider. See also the related `trusted_certificate_authority` configuration option. + // Optional. + string proxy_uri = 15; } diff --git a/src/common/http/http.cc b/src/common/http/http.cc index 652d06cd..1a71fb1c 100644 --- a/src/common/http/http.cc +++ b/src/common/http/http.cc @@ -237,13 +237,20 @@ absl::optional> Http::DecodeCookies( } Uri::Uri(absl::string_view uri) : pathQueryFragment_("/") { - if (uri.find(https_prefix_) != 0) { // must start with https:// - throw std::runtime_error(absl::StrCat("uri must be https scheme: ", uri)); + std::string scheme_prefix; + if (uri.find(https_prefix_) == 0) { + scheme_ = "https"; + scheme_prefix = https_prefix_; + } else if (uri.find(http_prefix_) == 0) { + scheme_ = "http"; + scheme_prefix = http_prefix_; + } else { + throw std::runtime_error(absl::StrCat("uri must be http or https scheme: ", uri)); } - if (uri.length() == https_prefix_.length()) { + if (uri.length() == scheme_prefix.length()) { throw std::runtime_error(absl::StrCat("no host in uri: ", uri)); } - auto uri_without_scheme = uri.substr(https_prefix_.length()); + auto uri_without_scheme = uri.substr(scheme_prefix.length()); std::string host_and_port; auto positions = {uri_without_scheme.find('/'), uri_without_scheme.find('?'), uri_without_scheme.find('#')}; @@ -280,14 +287,21 @@ Uri::Uri(absl::string_view uri) : pathQueryFragment_("/") { host_ = std::string(host_and_port.substr(0, colon_position).data(), colon_position); } else { host_ = host_and_port; + if (scheme_ == "http") { + port_ = 80; + } else if (scheme_ == "https") { + port_ = 443; + } } } const std::string Uri::https_prefix_ = "https://"; +const std::string Uri::http_prefix_ = "http://"; Uri &Uri::operator=(Uri &&uri) noexcept { host_ = uri.host_; port_ = uri.port_; + scheme_ = uri.scheme_; pathQueryFragmentString_ = uri.pathQueryFragmentString_; pathQueryFragment_ = uri.pathQueryFragment_; return *this; @@ -295,6 +309,7 @@ Uri &Uri::operator=(Uri &&uri) noexcept { Uri::Uri(const Uri &uri) : host_(uri.host_), + scheme_(uri.scheme_), port_(uri.port_), pathQueryFragmentString_(uri.pathQueryFragmentString_), pathQueryFragment_(uri.pathQueryFragment_) { @@ -338,8 +353,11 @@ PathQueryFragment::PathQueryFragment(absl::string_view path_query_fragment) { } response_t HttpImpl::Post(absl::string_view uri, - const std::map &headers, absl::string_view body, - absl::string_view ca_cert, boost::asio::io_context &ioc, + const std::map &headers, + absl::string_view body, + absl::string_view ca_cert, + absl::string_view proxy_uri, + boost::asio::io_context &ioc, boost::asio::yield_context yield) const { spdlog::trace("{}", __func__); try { @@ -368,9 +386,37 @@ response_t HttpImpl::Post(absl::string_view uri, throw boost::system::error_code{static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category()}; } - const auto results = - resolver.async_resolve(parsed_uri.GetHost(), std::to_string(parsed_uri.GetPort()), yield); - beast::get_lowest_layer(stream).async_connect(results, yield); + + if (!proxy_uri.empty()) { + auto parsed_proxy_uri = http::Uri(proxy_uri); + const auto results = resolver.async_resolve(parsed_proxy_uri.GetHost(), std::to_string(parsed_proxy_uri.GetPort()), yield); + beast::get_lowest_layer(stream).async_connect(results, yield); + + std::string target = absl::StrCat(parsed_uri.GetHost(), ":", std::to_string(parsed_uri.GetPort())); + beast::http::request http_connect_req{beast::http::verb::connect, target, version}; + http_connect_req.set(beast::http::field::host, target); + + // Send the HTTP connect request to the remote host + beast::http::async_write(stream.next_layer(), http_connect_req, yield); + + // Read the response from the server + boost::beast::flat_buffer http_connect_buffer; + beast::http::response http_connect_res; + beast::http::parser p(http_connect_res); + p.skip(true); // skip reading the body of the response because there won't be a body + + beast::http::async_read(stream.next_layer(), http_connect_buffer, p, yield); + if (http_connect_res.result() != beast::http::status::ok) { + throw std::runtime_error( + absl::StrCat("http connect failed with status: ", http_connect_res.result_int()) + ); + } + + } else { + const auto results = resolver.async_resolve(parsed_uri.GetHost(), std::to_string(parsed_uri.GetPort()), yield); + beast::get_lowest_layer(stream).async_connect(results, yield); + } + stream.async_handshake(ssl::stream_base::client, yield); // Set up an HTTP POST request message beast::http::request req{ diff --git a/src/common/http/http.h b/src/common/http/http.h index 0e17a270..82a12fd1 100644 --- a/src/common/http/http.h +++ b/src/common/http/http.h @@ -57,8 +57,10 @@ class PathQueryFragment { class Uri { private: static const std::string https_prefix_; + static const std::string http_prefix_; std::string host_; - int32_t port_ = 443; + std::string scheme_; + int32_t port_; std::string pathQueryFragmentString_; // includes the path, query, and fragment (if any) PathQueryFragment pathQueryFragment_; @@ -69,7 +71,7 @@ class Uri { Uri &operator=(Uri &&uri) noexcept; - inline std::string GetScheme() { return "https"; } + inline std::string GetScheme() { return scheme_; } inline std::string GetHost() { return host_; } @@ -185,13 +187,13 @@ class Http { * @param ca_cert the ca cert to be trusted in the http call * @return http response. */ - virtual response_t Post( - absl::string_view uri, - const std::map &headers, - absl::string_view body, - absl::string_view ca_cert, - boost::asio::io_context &ioc, - boost::asio::yield_context yield) const = 0; + virtual response_t Post(absl::string_view uri, + const std::map &headers, + absl::string_view body, + absl::string_view ca_cert, + absl::string_view proxy_uri, + boost::asio::io_context &ioc, + boost::asio::yield_context yield) const = 0; }; /** @@ -199,13 +201,13 @@ class Http { */ class HttpImpl : public Http { public: - response_t Post( - absl::string_view uri, - const std::map &headers, - absl::string_view body, - absl::string_view ca_cert, - boost::asio::io_context &ioc, - boost::asio::yield_context yield) const override; + response_t Post(absl::string_view uri, + const std::map &headers, + absl::string_view body, + absl::string_view ca_cert, + absl::string_view proxy_uri, + boost::asio::io_context &ioc, + boost::asio::yield_context yield) const override; }; } // namespace http diff --git a/src/config/get_config.cc b/src/config/get_config.cc index 2f33e65d..93635e6f 100644 --- a/src/config/get_config.cc +++ b/src/config/get_config.cc @@ -3,7 +3,6 @@ #include "spdlog/spdlog.h" #include #include -#include #include "config/config.pb.validate.h" #include "src/common/http/http.h" #include "absl/strings/string_view.h" @@ -16,16 +15,22 @@ using namespace google::protobuf::util; namespace authservice { namespace config { -void ValidateUri(absl::string_view uri, absl::string_view uri_name) { +void ValidateUri(absl::string_view uri, absl::string_view uri_name, absl::string_view required_scheme) { unique_ptr parsed_uri; try { parsed_uri = unique_ptr(new common::http::Uri(uri)); } catch (runtime_error &e) { + if (std::string(e.what()).find("uri must be http or https scheme") != std::string::npos) { + throw runtime_error(fmt::format("invalid {}: uri must be {} scheme: {}", uri_name, required_scheme, uri)); + } throw runtime_error(fmt::format("invalid {}: ", uri_name) + e.what()); } if (parsed_uri->HasQuery() || parsed_uri->HasFragment()) { throw runtime_error(fmt::format("invalid {}: query params and fragments not allowed: {}", uri_name, uri)); } + if (parsed_uri->GetScheme() != required_scheme) { + throw runtime_error(fmt::format("invalid {}: uri must be {} scheme: {}", uri_name, required_scheme, uri)); + } } unique_ptr GetConfig(const string &configFileName) { @@ -49,9 +54,13 @@ unique_ptr GetConfig(const string &configFileName) { } for (const auto &chain : config->chains()) { - ValidateUri(chain.filters(0).oidc().authorization_uri(), "authorization_uri"); - ValidateUri(chain.filters(0).oidc().callback_uri(), "callback_uri"); - ValidateUri(chain.filters(0).oidc().token_uri(), "token_uri"); + ValidateUri(chain.filters(0).oidc().authorization_uri(), "authorization_uri", "https"); + ValidateUri(chain.filters(0).oidc().callback_uri(), "callback_uri", "https"); + ValidateUri(chain.filters(0).oidc().token_uri(), "token_uri", "https"); + const auto proxy_uri = chain.filters(0).oidc().proxy_uri(); + if (!proxy_uri.empty()) { + ValidateUri(proxy_uri, "proxy_uri", "http"); + } } return config; diff --git a/src/filters/oidc/oidc_filter.cc b/src/filters/oidc/oidc_filter.cc index 659acca2..554ee5c3 100644 --- a/src/filters/oidc/oidc_filter.cc +++ b/src/filters/oidc/oidc_filter.cc @@ -441,7 +441,7 @@ std::shared_ptr OidcFilter::RefreshToken( spdlog::info("{}: POSTing to refresh access token", __func__); auto retrieved_token_response = http_ptr_->Post( idp_config_.token_uri(), headers, common::http::Http::EncodeFormData(params), - idp_config_.trusted_certificate_authority(), ioc, yield); + idp_config_.trusted_certificate_authority(), idp_config_.proxy_uri(), ioc, yield); if (retrieved_token_response == nullptr) { spdlog::warn("{}: Received null pointer as response from identity provider.", __func__); @@ -512,7 +512,7 @@ google::rpc::Code OidcFilter::RetrieveToken( auto retrieve_token_response = http_ptr_->Post( idp_config_.token_uri(), headers, common::http::Http::EncodeFormData(params), - idp_config_.trusted_certificate_authority(), ioc, yield); + idp_config_.trusted_certificate_authority(), idp_config_.proxy_uri(), ioc, yield); if (retrieve_token_response == nullptr) { spdlog::info("{}: HTTP error encountered: {}", __func__, "IdP connection error"); diff --git a/test/common/http/http_test.cc b/test/common/http/http_test.cc index 2c62e460..c241a811 100644 --- a/test/common/http/http_test.cc +++ b/test/common/http/http_test.cc @@ -182,6 +182,15 @@ TEST(Http, ParseUri) { ASSERT_EQ(result.GetQuery(), ""); ASSERT_EQ(result.GetFragment(), ""); + result = Uri("http://www.example.com:1234"); + ASSERT_EQ(result.GetScheme(), "http"); + ASSERT_EQ(result.GetHost(), "www.example.com"); + ASSERT_EQ(result.GetPort(), 1234); + ASSERT_EQ(result.GetPathQueryFragment(), "/"); + ASSERT_EQ(result.GetPath(), "/"); + ASSERT_EQ(result.GetQuery(), ""); + ASSERT_EQ(result.GetFragment(), ""); + result = Uri("https://www.example.com:1234/path"); ASSERT_EQ(result.GetScheme(), "https"); ASSERT_EQ(result.GetHost(), "www.example.com"); @@ -236,9 +245,19 @@ TEST(Http, ParseUri) { ASSERT_EQ(result.GetQuery(), "query"); ASSERT_EQ(result.GetFragment(), "frag/?ment"); - ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("noscheme"); }, "uri must be https scheme: noscheme"); - ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("not_https://host"); }, "uri must be https scheme: not_https://host"); + result = Uri("http://www.example.com?query#frag/?ment"); + ASSERT_EQ(result.GetScheme(), "http"); + ASSERT_EQ(result.GetHost(), "www.example.com"); + ASSERT_EQ(result.GetPort(), 80); + ASSERT_EQ(result.GetPathQueryFragment(), "/?query#frag/?ment"); + ASSERT_EQ(result.GetPath(), "/"); + ASSERT_EQ(result.GetQuery(), "query"); + ASSERT_EQ(result.GetFragment(), "frag/?ment"); + + ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("noscheme"); }, "uri must be http or https scheme: noscheme"); + ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("ftp://host"); }, "uri must be http or https scheme: ftp://host"); ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("https://"); }, "no host in uri: https://"); // no host + ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("http://"); }, "no host in uri: http://"); // no host ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("https://:80/path"); }, "no host in uri: https://:80/path"); // no host ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("https://host:/path"); }, "port not valid in uri: https://host:/path"); // colon, but no port ASSERT_THROWS_STD_RUNTIME_ERROR([]() -> void { Uri("https://host:a8/path"); }, "port not valid in uri: https://host:a8/path"); // port not an int diff --git a/test/common/http/mocks.h b/test/common/http/mocks.h index 8cba2a0e..935b5107 100644 --- a/test/common/http/mocks.h +++ b/test/common/http/mocks.h @@ -9,11 +9,12 @@ namespace common { namespace http { class HttpMock : public Http { public: - MOCK_CONST_METHOD6(Post, response_t( + MOCK_CONST_METHOD7(Post, response_t( absl::string_view uri, const std::map &headers, absl::string_view body, absl::string_view ca_cert, + absl::string_view proxy_uri, boost::asio::io_context &ioc, boost::asio::yield_context yield)); }; diff --git a/test/config/getconfig_test.cc b/test/config/getconfig_test.cc index 76453254..f335efbd 100644 --- a/test/config/getconfig_test.cc +++ b/test/config/getconfig_test.cc @@ -28,6 +28,7 @@ class GetConfigTest : public ::testing::Test { "authorization_uri": "{}", "token_uri": "{}", "callback_uri": "{}", + "proxy_uri": "{}", "jwks": "fake-jwks", "client_id": "fake-client-id", "client_secret": "fake-client-secret", @@ -59,6 +60,7 @@ class GetConfigTest : public ::testing::Test { "authorization_uri": "{}", "token_uri": "{}", "callback_uri": "{}", + "proxy_uri": "{}", "jwks": "fake-jwks", "client_id": "fake-client-id", "client_secret": "fake-client-secret", @@ -79,6 +81,7 @@ class GetConfigTest : public ::testing::Test { "authorization_uri": "{}", "token_uri": "{}", "callback_uri": "{}", + "proxy_uri": "{}", "jwks": "fake-jwks", "client_id": "fake-client-id", "client_secret": "fake-client-secret", @@ -153,6 +156,8 @@ TEST_F(GetConfigTest, ReturnsTheConfig) { ASSERT_EQ(oidc.idle_session_timeout(), 600); ASSERT_EQ(oidc.trusted_certificate_authority(), "ca_placeholder"); + + ASSERT_EQ(oidc.proxy_uri(), "http://proxy.example.com"); } TEST_F(GetConfigTest, ValidateOidcConfigThrowsForInvalidConfig) { @@ -192,75 +197,103 @@ TEST_F(GetConfigTest, ValidateOidcConfigThrowsForInvalidConfigForUriNestedProper } TEST_F(GetConfigTest, ValidatesTheUris) { - write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz")); + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz", "http://proxy")); + ASSERT_NO_THROW(GetConfig(tmp_filename)); + + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz", "")); ASSERT_NO_THROW(GetConfig(tmp_filename)); - write_test_file(fmt::format(minimal_valid_config, "invalid", "https://bar", "https://baz")); + write_test_file(fmt::format(minimal_valid_config, "invalid", "https://bar", "https://baz", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid authorization_uri: uri must be https scheme: invalid"); - write_test_file(fmt::format(minimal_valid_config, "https://foo", "invalid", "https://baz")); + write_test_file(fmt::format(minimal_valid_config, "https://foo", "invalid", "https://baz", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid token_uri: uri must be https scheme: invalid"); - write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "invalid")); + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "invalid", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid callback_uri: uri must be https scheme: invalid"); - write_test_file(fmt::format(minimal_valid_config, "https://foo?q=2", "https://bar", "https://baz")); + write_test_file(fmt::format(minimal_valid_config, "https://foo?q=2", "https://bar", "https://baz", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid authorization_uri: query params and fragments not allowed: https://foo?q=2"); - write_test_file(fmt::format(minimal_valid_config, "https://foo#2", "https://bar", "https://baz")); + write_test_file(fmt::format(minimal_valid_config, "https://foo#2", "https://bar", "https://baz", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid authorization_uri: query params and fragments not allowed: https://foo#2"); - write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar?q=2", "https://baz")); + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar?q=2", "https://baz", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid token_uri: query params and fragments not allowed: https://bar?q=2"); - write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar#2", "https://baz")); + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar#2", "https://baz", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid token_uri: query params and fragments not allowed: https://bar#2"); - write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz?q=2")); + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz?q=2", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid callback_uri: query params and fragments not allowed: https://baz?q=2"); - write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz#2")); + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz#2", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid callback_uri: query params and fragments not allowed: https://baz#2"); + + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz", "https://proxy")); + ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, + "invalid proxy_uri: uri must be http scheme: https://proxy"); + + write_test_file(fmt::format(minimal_valid_config, "https://foo", "https://bar", "https://baz", "https://proxy?q")); + ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, + "invalid proxy_uri: query params and fragments not allowed: https://proxy?q"); } TEST_F(GetConfigTest, ValidatesTheUris_WhenThereAreMultipleChains) { write_test_file(fmt::format(multiple_chains_valid_config, - "https://foo1", "https://bar1", "https://baz1", - "https://foo2", "https://bar2", "https://baz2")); + "https://foo1", "https://bar1", "https://baz1", "", + "https://foo2", "https://bar2", "https://baz2", "")); + ASSERT_NO_THROW(GetConfig(tmp_filename)); + + write_test_file(fmt::format(multiple_chains_valid_config, + "https://foo1", "https://bar1", "https://baz1", "http://proxy", + "https://foo2", "https://bar2", "https://baz2", "http://proxy")); ASSERT_NO_THROW(GetConfig(tmp_filename)); write_test_file(fmt::format(multiple_chains_valid_config, - "invalid", "https://bar1", "https://baz1", - "https://foo2", "https://bar2", "https://baz2")); + "invalid", "https://bar1", "https://baz1", "", + "https://foo2", "https://bar2", "https://baz2", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid authorization_uri: uri must be https scheme: invalid"); write_test_file(fmt::format(multiple_chains_valid_config, - "https://foo1", "https://bar1", "https://baz1", - "invalid", "https://bar2", "https://baz2")); + "https://foo1", "https://bar1", "https://baz1", "", + "invalid", "https://bar2", "https://baz2", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid authorization_uri: uri must be https scheme: invalid"); write_test_file(fmt::format(multiple_chains_valid_config, - "https://foo1", "https://bar1", "https://baz1", - "https://foo2", "invalid", "https://baz2")); + "https://foo1", "https://bar1", "https://baz1", "", + "https://foo2", "invalid", "https://baz2", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid token_uri: uri must be https scheme: invalid"); write_test_file(fmt::format(multiple_chains_valid_config, - "https://foo1", "https://bar1", "https://baz1", - "https://foo2", "https://bar2", "invalid")); + "https://foo1", "https://bar1", "https://baz1", "", + "https://foo2", "https://bar2", "invalid", "")); ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, "invalid callback_uri: uri must be https scheme: invalid"); + + write_test_file(fmt::format(multiple_chains_valid_config, + "https://foo1", "https://bar1", "https://baz1", "https://proxy", + "https://foo2", "https://bar2", "https://baz2", "")); + ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, + "invalid proxy_uri: uri must be http scheme: https://proxy"); + + write_test_file(fmt::format(multiple_chains_valid_config, + "https://foo1", "https://bar1", "https://baz1", "", + "https://foo2", "https://bar2", "https://baz2", "https://proxy")); + ASSERT_THROWS_STD_RUNTIME_ERROR([this] { GetConfig(tmp_filename); }, + "invalid proxy_uri: uri must be http scheme: https://proxy"); } } // namespace config diff --git a/test/filters/oidc/oidc_filter_test.cc b/test/filters/oidc/oidc_filter_test.cc index a5d33b25..78a4485b 100644 --- a/test/filters/oidc/oidc_filter_test.cc +++ b/test/filters/oidc/oidc_filter_test.cc @@ -94,6 +94,7 @@ class OidcFilterTest : public ::testing::Test { config_.mutable_id_token()->set_header("authorization"); config_.mutable_id_token()->set_preamble("Bearer"); config_.set_trusted_certificate_authority("some-ca"); + config_.set_proxy_uri("http://some-proxy-uri.com"); config_.set_callback_uri("https://me.tld/callback"); callback_host_ = "me.tld:443"; @@ -335,7 +336,7 @@ TEST_F(OidcFilterTest, auto *pMessage = new beast::http::response(); auto raw_http_token_response_from_idp = common::http::response_t(pMessage); raw_http_token_response_from_idp->result(beast::http::status::ok); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)).WillOnce( + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)).WillOnce( Return(ByMove(std::move(raw_http_token_response_from_idp)))); auto jwt_status = test_id_token_jwt_.parseFromString(test_id_token_jwt_string_); @@ -418,7 +419,7 @@ TEST_F(OidcFilterTest, Process_RedirectsUsersToAuthenticate_WhenFailingToParseTh auto *pMessage = new beast::http::response(); auto raw_http_token_response_from_idp = common::http::response_t(pMessage); raw_http_token_response_from_idp->result(beast::http::status::ok); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)).WillOnce( + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)).WillOnce( Return(ByMove(std::move(raw_http_token_response_from_idp)))); EXPECT_CALL(*parser_mock_, ParseRefreshTokenResponse(_, _)).WillOnce(::testing::Return(nullptr)); @@ -460,7 +461,7 @@ TEST_F(OidcFilterTest, Process_RedirectsUsersToAuthenticate_WhenFailingToEstabli SetExpiredAccessTokenResponseInSessionStore(); auto mocked_http = new common::http::HttpMock(); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)).WillOnce(Return(ByMove(nullptr))); + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)).WillOnce(Return(ByMove(nullptr))); auto old_session_id = std::string("session123"); auto new_session_id = std::string("session456"); @@ -501,7 +502,7 @@ TEST_F(OidcFilterTest, Process_RedirectsUsersToAuthenticate_WhenIDPReturnsUnsucc auto *pMessage = new beast::http::response(); auto raw_http_token_response_from_idp = common::http::response_t(pMessage); raw_http_token_response_from_idp->result(beast::http::status::bad_request); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)).WillOnce( + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)).WillOnce( Return(ByMove(std::move(raw_http_token_response_from_idp)))); // we want the code to return before attempting to parse the bad response @@ -763,7 +764,7 @@ TEST_F(OidcFilterTest, RetrieveToken_ReturnsError_WhenTokenResponseIsMissingAcce auto raw_http = common::http::response_t( new beast::http::response()); raw_http->result(beast::http::status::ok); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)) + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)) .WillOnce(Return(ByMove(std::move(raw_http)))); ASSERT_FALSE(session_store_->GetTokenResponse(session_id)); OidcFilter filter(common::http::ptr_t(mocked_http), config_, parser_mock_, session_string_generator_mock_, @@ -876,7 +877,7 @@ TEST_F(OidcFilterTest, RetrieveToken_ReturnsError_WhenBrokenPipe) { auto *mocked_http = new common::http::HttpMock(); auto raw_http = common::http::response_t(); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)) + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)) .WillOnce(Return(ByMove(std::move(raw_http)))); OidcFilter filter(common::http::ptr_t(mocked_http), config_, parser_mock_, session_string_generator_mock_, session_store_); @@ -911,7 +912,7 @@ TEST_F(OidcFilterTest, RetrieveToken_ReturnsError_WhenInvalidResponse) { auto *mocked_http = new common::http::HttpMock(); auto raw_http = common::http::response_t( (new beast::http::response())); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)) + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)) .WillOnce(Return(ByMove(std::move(raw_http)))); OidcFilter filter(common::http::ptr_t(mocked_http), config_, parser_mock_, session_string_generator_mock_, session_store_); @@ -978,7 +979,7 @@ void OidcFilterTest::AssertRetrieveToken(config::oidc::OIDCConfig &oidcConfig, s auto raw_http = common::http::response_t( new beast::http::response()); raw_http->result(beast::http::status::ok); - EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), _, _)) + EXPECT_CALL(*mocked_http, Post(Eq(token_uri), _, _, Eq("some-ca"), Eq("http://some-proxy-uri.com"), _, _)) .WillOnce(Return(ByMove(std::move(raw_http)))); OidcFilter filter(common::http::ptr_t(mocked_http), oidcConfig, parser_mock_, session_string_generator_mock_, session_store_); diff --git a/test/fixtures/valid-config.json b/test/fixtures/valid-config.json index e93e998a..7502021c 100644 --- a/test/fixtures/valid-config.json +++ b/test/fixtures/valid-config.json @@ -33,6 +33,7 @@ "jwks": "jwks_placeholder", "client_id": "foo", "client_secret": "bar", + "proxy_uri": "http://proxy.example.com", "scopes": [ "scope" ],