Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
Signed-off-by: Shikugawa <[email protected]>
  • Loading branch information
Shikugawa committed Aug 23, 2021
1 parent 2b26ee6 commit b83bad8
Show file tree
Hide file tree
Showing 20 changed files with 438 additions and 258 deletions.
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ The configuration of an OpenID Connect filter that can be used to retrieve ident
| authorization_uri | The OIDC Provider's [authorization endpoint](https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint). Required. | string |
| token_uri | The OIDC Provider's [token endpoint](https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint). Required. | string |
| callback_uri | This value will be used as the `redirect_uri` param of the authorization code grant [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). This URL must be one of the Redirection URI values for the Client pre-registered at the OIDC provider. Note: The Istio gateway's VirtualService must be prepared to ensure that this URL will get routed to the service so that the Authservice can intercept the request and handle it (see [example](https://github.com/istio-ecosystem/authservice/blob/master/bookinfo-example/config/bookinfo-gateway.yaml)). Required. | string |
| jwks_config | | oneof |
| jwks | The JSON JWKS response from the OIDC provider’s `jwks_uri` URI which can be found in the OIDC provider's [configuration response](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse). Note that this JSON value must be escaped when embedded in a json configmap (see [example](https://github.com/istio-ecosystem/authservice/blob/master/bookinfo-example/config/authservice-configmap-template.yaml)). Used during token verification. Required. | string |
| jwks_uri | | string |
| client_id | The OIDC client ID assigned to the filter to be used in the [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). Required. | string |
| client_secret | The OIDC client secret assigned to the filter to be used in the [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). Required. | string |
| scopes | Additional scopes passed to the OIDC Provider in the [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). The `openid` scope is always sent to the OIDC Provider, and does not need to be specified here. Required, but an empty array is allowed. | (slice of) string |
Expand Down
34 changes: 34 additions & 0 deletions run/config/config_jwk_uri.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"listen_address": "0.0.0.0",
"listen_port": "10004",
"log_level": "trace",
"threads": 8,
"chains": [
{
"name": "idp_filter_chain",
"filters": [
{
"oidc":
{
"authorization_uri": "https://localhost:8443/auth/realms/master/protocol/openid-connect/auth",
"token_uri": "https://localhost:8443/auth/realms/master/protocol/openid-connect/token",
"client_id": "test-istio",
"client_secret": "01d4c175-e090-40ce-9182-a585cb19b0f5",
"callback_uri": "https://localhost:9000/oauth/callback",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"scopes": [],
"id_token": {
"preamble": "Bearer",
"header": "Authorization"
},
"logout": {
"path": "/authservice_logout",
"redirect_uri": "https://google.com"
}
}
}
]
}
]
}

252 changes: 159 additions & 93 deletions src/common/http/http.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <boost/asio/spawn.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <sstream>

#include "absl/strings/escaping.h"
Expand Down Expand Up @@ -369,12 +371,12 @@ PathQueryFragment::PathQueryFragment(absl::string_view path_query_fragment) {
}
}

response_t HttpImpl::DoRequest(
response_t HttpImpl::Post(
absl::string_view uri,
const std::map<absl::string_view, absl::string_view> &headers,
absl::string_view body, absl::string_view ca_cert, beast::http::verb method,
absl::string_view body, absl::string_view ca_cert,
absl::string_view proxy_uri, boost::asio::io_context &ioc,
boost::asio::yield_context yield) {
boost::asio::yield_context yield) const {
spdlog::trace("{}", __func__);
try {
int version = 11;
Expand Down Expand Up @@ -448,111 +450,175 @@ response_t HttpImpl::DoRequest(
}

stream.async_handshake(ssl::stream_base::client, yield);

switch (method) {
case beast::http::verb::post:
return Post(version, parsed_uri, headers, body, stream, yield);
case beast::http::verb::get:
return Get(version, parsed_uri, headers, stream, yield);
default:
spdlog::info("unsupported method");
return response_t{};
// Set up an HTTP POST request message
beast::http::request<beast::http::string_body> req{
beast::http::verb::post, parsed_uri.GetPathQueryFragment(), version};
req.set(beast::http::field::host, parsed_uri.GetHost());
for (auto header : headers) {
req.set(boost::beast::string_view(header.first.data()),
boost::beast::string_view(header.second.data()));
}
auto &req_body = req.body();
req_body.reserve(body.size());
req_body.append(body.begin(), body.end());
req.prepare_payload();
// Send the HTTP request to the remote host
beast::http::async_write(stream, req, yield);

// Read response
beast::flat_buffer buffer;
response_t res(new beast::http::response<beast::http::string_body>);
beast::http::async_read(stream, buffer, *res, yield);

// Gracefully close the socket.
// Receive an error code instead of throwing an exception if this fails, so
// we can ignore some expected not_connected errors.
boost::system::error_code ec;
stream.async_shutdown(yield[ec]);

if (ec) {
// not_connected happens sometimes so don't bother reporting it.
// stream_truncated also happens sometime and we choose to ignore the
// stream_truncated error, as recommended by the github thread:
// https://github.com/boostorg/beast/issues/824
if (ec != beast::errc::not_connected &&
ec != boost::asio::ssl::error::stream_truncated) {
spdlog::info("{}: HTTP error encountered: {}", __func__, ec.message());
return response_t();
}
}

return res;
// If we get here then the connection is closed gracefully
} catch (std::exception const &e) {
spdlog::info("{}: unexpected exception: {}", __func__, e.what());
return response_t();
}
}

response_t HttpImpl::Post(
int version, Uri uri,
response_t HttpImpl::Get(
absl::string_view uri,
const std::map<absl::string_view, absl::string_view> &headers,
absl::string_view body, beast::ssl_stream<beast::tcp_stream> &stream,
boost::asio::yield_context &yield) {
// Set up an HTTP POST request message
beast::http::request<beast::http::string_body> req{
beast::http::verb::post, uri.GetPathQueryFragment(), version};
req.set(beast::http::field::host, uri.GetHost());
for (auto header : headers) {
req.set(boost::beast::string_view(header.first.data()),
boost::beast::string_view(header.second.data()));
}
auto &req_body = req.body();
req_body.reserve(body.size());
req_body.append(body.begin(), body.end());
req.prepare_payload();
// Send the HTTP request to the remote host
beast::http::async_write(stream, req, yield);

// Read response
beast::flat_buffer buffer;
response_t res(new beast::http::response<beast::http::string_body>);
beast::http::async_read(stream, buffer, *res, yield);

// Gracefully close the socket.
// Receive an error code instead of throwing an exception if this fails, so
// we can ignore some expected not_connected errors.
boost::system::error_code ec;
stream.async_shutdown(yield[ec]);

if (ec) {
// not_connected happens sometimes so don't bother reporting it.
// stream_truncated also happens sometime and we choose to ignore the
// stream_truncated error, as recommended by the github thread:
// https://github.com/boostorg/beast/issues/824
if (ec != beast::errc::not_connected &&
ec != boost::asio::ssl::error::stream_truncated) {
spdlog::info("{}: HTTP error encountered: {}", __func__, ec.message());
return response_t();
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 {
int version = 11;

ssl::context ctx(ssl::context::tlsv12_client);
ctx.set_verify_mode(ssl::verify_peer);
ctx.set_default_verify_paths();

if (!ca_cert.empty()) {
spdlog::info("{}: Trusting the provided certificate authority", __func__);
beast::error_code ca_ec;
ctx.add_certificate_authority(
boost::asio::buffer(ca_cert.data(), ca_cert.size()), ca_ec);
if (ca_ec) {
throw boost::system::system_error{ca_ec};
}
}
}

return res;
}
auto parsed_uri = http::Uri(uri);

response_t HttpImpl::Get(
int version, Uri uri,
const std::map<absl::string_view, absl::string_view> &headers,
beast::ssl_stream<beast::tcp_stream> &stream,
boost::asio::yield_context &yield) {
// Set up an HTTP GET request message
beast::http::request<beast::http::string_body> req{
beast::http::verb::get, uri.GetPathQueryFragment(), version};
req.set(beast::http::field::host, uri.GetHost());
for (auto header : headers) {
req.set(boost::beast::string_view(header.first.data()),
boost::beast::string_view(header.second.data()));
}
req.prepare_payload();
// Send the HTTP request to the remote host
beast::http::async_write(stream, req, yield);

// Read response
beast::flat_buffer buffer;
response_t res(new beast::http::response<beast::http::string_body>);
beast::http::async_read(stream, buffer, *res, yield);

// Gracefully close the socket.
// Receive an error code instead of throwing an exception if this fails, so
// we can ignore some expected not_connected errors.
boost::system::error_code ec;
stream.async_shutdown(yield[ec]);

if (ec) {
// not_connected happens sometimes so don't bother reporting it.
// stream_truncated also happens sometime and we choose to ignore the
// stream_truncated error, as recommended by the github thread:
// https://github.com/boostorg/beast/issues/824
if (ec != beast::errc::not_connected &&
ec != boost::asio::ssl::error::stream_truncated) {
spdlog::info("{}: HTTP error encountered: {}", __func__, ec.message());
return response_t();
tcp::resolver resolver(ioc);
beast::ssl_stream<beast::tcp_stream> stream(ioc, ctx);
if (!SSL_set_tlsext_host_name(stream.native_handle(),
parsed_uri.GetHost().c_str())) {
throw boost::system::error_code{static_cast<int>(::ERR_get_error()),
boost::asio::error::get_ssl_category()};
}
}

return res;
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);
spdlog::info(
"{}: opening connection to proxy {} for request to destination {}:{}",
__func__, proxy_uri.data(), parsed_uri.GetHost(),
parsed_uri.GetPort());
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<beast::http::string_body> 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<beast::http::empty_body> http_connect_res;
beast::http::parser<false, beast::http::empty_body> 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 {
spdlog::info("{}: opening connection to {}:{}", __func__,
parsed_uri.GetHost(), parsed_uri.GetPort());
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<beast::http::string_body> req{
beast::http::verb::get, parsed_uri.GetPathQueryFragment(), version};
req.set(beast::http::field::host, parsed_uri.GetHost());
for (auto header : headers) {
req.set(boost::beast::string_view(header.first.data()),
boost::beast::string_view(header.second.data()));
}
auto &req_body = req.body();
req_body.reserve(body.size());
req_body.append(body.begin(), body.end());
req.prepare_payload();
// Send the HTTP request to the remote host
beast::http::async_write(stream, req, yield);

// Read response
beast::flat_buffer buffer;
response_t res(new beast::http::response<beast::http::string_body>);
beast::http::async_read(stream, buffer, *res, yield);

// Gracefully close the socket.
// Receive an error code instead of throwing an exception if this fails, so
// we can ignore some expected not_connected errors.
boost::system::error_code ec;
stream.async_shutdown(yield[ec]);

if (ec) {
// not_connected happens sometimes so don't bother reporting it.
// stream_truncated also happens sometime and we choose to ignore the
// stream_truncated error, as recommended by the github thread:
// https://github.com/boostorg/beast/issues/824
if (ec != beast::errc::not_connected &&
ec != boost::asio::ssl::error::stream_truncated) {
spdlog::info("{}: HTTP error encountered: {}", __func__, ec.message());
return response_t();
}
}

return res;
// If we get here then the connection is closed gracefully
} catch (std::exception const &e) {
spdlog::info("{}: unexpected exception: {}", __func__, e.what());
return response_t();
}
}

} // namespace http
Expand Down
Loading

0 comments on commit b83bad8

Please sign in to comment.