Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,13 @@ def _com_google_absl():
actual = "@com_google_absl//absl/debugging:symbolize",
)

# Require abseil_time as an indirect dependency as it is needed by the
# direct dependency jwt_verify_lib.
native.bind(
name = "abseil_time",
actual = "@com_google_absl//absl/time:time",
)

def _com_google_protobuf():
_repository_impl("com_google_protobuf")

Expand Down
2 changes: 1 addition & 1 deletion bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ REPOSITORY_LOCATIONS = dict(
remote = "https://github.com/google/googleapis",
),
com_github_google_jwt_verify = dict(
commit = "4eb9e96485b71e00d43acc7207501caafb085b4a", # 2018-06-11
commit = "66792a057ec54e4b75c6a2eeda4e98220bd12a9a", # 2018-08-17
remote = "https://github.com/google/jwt_verify_lib",
),
com_github_nodejs_http_parser = dict(
Expand Down
14 changes: 14 additions & 0 deletions source/extensions/filters/http/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ envoy_cc_library(
"//include/envoy/server:filter_config_interface",
],
)

envoy_cc_library(
name = "jwks_fetcher_lib",
srcs = ["jwks_fetcher.cc"],
hdrs = ["jwks_fetcher.h"],
external_deps = [
"jwt_verify_lib",
],
deps = [
"//include/envoy/upstream:cluster_manager_interface",
"//source/common/http:utility_lib",
"@envoy_api//envoy/api/v2/core:http_uri_cc",
],
)
106 changes: 106 additions & 0 deletions source/extensions/filters/http/common/jwks_fetcher.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include "extensions/filters/http/common/jwks_fetcher.h"

#include "common/common/enum_to_int.h"
#include "common/http/headers.h"
#include "common/http/utility.h"

#include "jwt_verify_lib/status.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace Common {
namespace {

class JwksFetcherImpl : public JwksFetcher,
public Logger::Loggable<Logger::Id::filter>,
public Http::AsyncClient::Callbacks {
public:
JwksFetcherImpl(Upstream::ClusterManager& cm) : cm_(cm) { ENVOY_LOG(trace, "{}", __func__); }

~JwksFetcherImpl() { cancel(); }

void cancel() {
if (request_ && !complete_) {
request_->cancel();
ENVOY_LOG(debug, "fetch pubkey [uri = {}]: canceled", uri_->uri());
}
reset();
}

void fetch(const ::envoy::api::v2::core::HttpUri& uri, JwksFetcher::JwksReceiver& receiver) {
ENVOY_LOG(trace, "{}", __func__);
ASSERT(!receiver_);
complete_ = false;
receiver_ = &receiver;
uri_ = &uri;
Http::MessagePtr message = Http::Utility::prepareHeaders(uri);
message->headers().insertMethod().value().setReference(Http::Headers::get().MethodValues.Get);
ENVOY_LOG(debug, "fetch pubkey from [uri = {}]: start", uri_->uri());
request_ =
cm_.httpAsyncClientForCluster(uri.cluster())
.send(std::move(message), *this,
std::chrono::milliseconds(DurationUtil::durationToMilliseconds(uri.timeout())));
}

// HTTP async receive methods
void onSuccess(Http::MessagePtr&& response) {
ENVOY_LOG(trace, "{}", __func__);
complete_ = true;
const uint64_t status_code = Http::Utility::getResponseStatus(response->headers());
if (status_code == enumToInt(Http::Code::OK)) {
ENVOY_LOG(debug, "{}: fetch pubkey [uri = {}]: success", __func__, uri_->uri());
if (response->body()) {
const auto len = response->body()->length();
const auto body = std::string(static_cast<char*>(response->body()->linearize(len)), len);
auto jwks =
google::jwt_verify::Jwks::createFrom(body, google::jwt_verify::Jwks::Type::JWKS);
if (jwks->getStatus() == google::jwt_verify::Status::Ok) {
ENVOY_LOG(debug, "{}: fetch pubkey [uri = {}]: succeeded", __func__, uri_->uri());
receiver_->onJwksSuccess(std::move(jwks));
} else {
ENVOY_LOG(debug, "{}: fetch pubkey [uri = {}]: invalid jwks", __func__, uri_->uri());
receiver_->onJwksError(JwksFetcher::JwksReceiver::Failure::InvalidJwks);
}
} else {
ENVOY_LOG(debug, "{}: fetch pubkey [uri = {}]: body is empty", __func__, uri_->uri());
receiver_->onJwksError(JwksFetcher::JwksReceiver::Failure::Network);
}
} else {
ENVOY_LOG(debug, "{}: fetch pubkey [uri = {}]: response status code {}", __func__,
uri_->uri(), status_code);
receiver_->onJwksError(JwksFetcher::JwksReceiver::Failure::Network);
}
reset();
}

void onFailure(Http::AsyncClient::FailureReason reason) {
ENVOY_LOG(debug, "{}: fetch pubkey [uri = {}]: network error {}", __func__, uri_->uri(),
enumToInt(reason));
complete_ = true;
receiver_->onJwksError(JwksFetcher::JwksReceiver::Failure::Network);
reset();
}

private:
Upstream::ClusterManager& cm_;
bool complete_{};
JwksFetcher::JwksReceiver* receiver_{};
const envoy::api::v2::core::HttpUri* uri_{};
Http::AsyncClient::Request* request_{};

void reset() {
request_ = nullptr;
receiver_ = nullptr;
uri_ = nullptr;
}
};
} // namespace

JwksFetcherPtr JwksFetcher::create(Upstream::ClusterManager& cm) {
return std::make_unique<JwksFetcherImpl>(cm);
}
} // namespace Common
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
75 changes: 75 additions & 0 deletions source/extensions/filters/http/common/jwks_fetcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#pragma once

#include "envoy/api/v2/core/http_uri.pb.h"
#include "envoy/common/pure.h"
#include "envoy/upstream/cluster_manager.h"

#include "jwt_verify_lib/jwks.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace Common {

class JwksFetcher;
typedef std::unique_ptr<JwksFetcher> JwksFetcherPtr;
/**
* JwksFetcher interface can be used to retrieve remote JWKS
* (https://tools.ietf.org/html/rfc7517) data structures returning a concrete,
* type-safe representation. An instance of this interface is designed to
Copy link
Member

Choose a reason for hiding this comment

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

Ah, you answer the above question here. This is a pretty weird pattern; I would call it out by explicitly naming the class/interface JwksSingleUseFetcher or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@htuch I've improved the description of the functionality in 8fa1dc8. I've also made this class re-usable but designed to retrieve a single JWKS at a time.

* retrieve one JWKS at a time.
*/
class JwksFetcher {
public:
class JwksReceiver {
public:
enum class Failure {
/* A network error occured causing JWKS retrieval failure. */
Network,
/* A failure occured when trying to parse the retrieved JWKS data. */
InvalidJwks,
};

virtual ~JwksReceiver(){};
/*
* Successful retrieval callback.
* of the returned JWKS object.
* @param jwks the JWKS object retrieved.
*/
virtual void onJwksSuccess(google::jwt_verify::JwksPtr&& jwks) PURE;
/*
* Retrieval error callback.
* * @param reason the failure reason.
*/
virtual void onJwksError(Failure reason) PURE;
};

virtual ~JwksFetcher(){};

/*
* Cancel any in-flight request.
*/
virtual void cancel() PURE;

/*
* Retrieve a JWKS resource from a remote HTTP host.
Copy link
Member

Choose a reason for hiding this comment

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

Retrieve a JWKS resource from a remote HTTP host. At most one outstanding request may be in-flight, i.e. from the invocation of `fetch()` until either a callback or `cancel()` is invoked, no additional `fetch()` may be issued.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 2d18a01

* At most one outstanding request may be in-flight,
* i.e. from the invocation of `fetch()` until either
* a callback or `cancel()` is invoked, no
* additional `fetch()` may be issued.
* @param uri the uri to retrieve the jwks from.
* @param receiver the receiver of the fetched JWKS or error.
*/
virtual void fetch(const ::envoy::api::v2::core::HttpUri& uri, JwksReceiver& receiver) PURE;

/*
* Factory method for creating a JwksFetcher.
* @param cm the cluster manager to use during Jwks retrieval
* @return a JwksFetcher instance
*/
static JwksFetcherPtr create(Upstream::ClusterManager& cm);
};
} // namespace Common
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
1 change: 1 addition & 0 deletions source/extensions/filters/http/jwt_authn/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ envoy_cc_library(
"//include/envoy/server:filter_config_interface",
"//include/envoy/stats:stats_macros",
"//source/common/http:message_lib",
"//source/extensions/filters/http/common:jwks_fetcher_lib",
],
)

Expand Down
Loading