Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ def _com_google_absl():
name = "abseil_symbolize",
actual = "@com_google_absl//absl/debugging:symbolize",
)
native.bind(
Comment thread
nickrmc83 marked this conversation as resolved.
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 @@ -70,7 +70,7 @@ REPOSITORY_LOCATIONS = dict(
remote = "https://github.com/google/googleapis",
),
com_github_google_jwt_verify = dict(
commit = "4eb9e96485b71e00d43acc7207501caafb085b4a",
commit = "66792a057ec54e4b75c6a2eeda4e98220bd12a9a",
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",
],
)
91 changes: 91 additions & 0 deletions source/extensions/filters/http/common/jwks_fetcher.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#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,
Comment thread
nickrmc83 marked this conversation as resolved.
public Logger::Loggable<Logger::Id::filter>,
public Http::AsyncClient::Callbacks {
private:
Comment thread
nickrmc83 marked this conversation as resolved.
Outdated
Upstream::ClusterManager& cm_;
JwksFetcher::JwksReceiver* receiver_ = nullptr;
Comment thread
nickrmc83 marked this conversation as resolved.
Outdated
const ::envoy::api::v2::core::HttpUri* uri_ = nullptr;
Comment thread
nickrmc83 marked this conversation as resolved.
Outdated
Http::AsyncClient::Request* request_ = nullptr;

public:
JwksFetcherImpl(Upstream::ClusterManager& cm) : cm_(cm) { ENVOY_LOG(trace, "{}", __func__); }

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

void fetch(const ::envoy::api::v2::core::HttpUri& uri, JwksFetcher::JwksReceiver* receiver) {
ENVOY_LOG(trace, "{}", __func__);
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__);
request_ = nullptr;
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::invalid_jwks);
}
} 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);
}
}

void onFailure(Http::AsyncClient::FailureReason reason) {
Comment thread
nickrmc83 marked this conversation as resolved.
ENVOY_LOG(debug, "{}: fetch pubkey [uri = {}]: network error {}", __func__, uri_->uri(),
enumToInt(reason));
receiver_->onJwksError(JwksFetcher::JwksReceiver::Failure::network);
}
};
} // namespace

JwksFetcherPtr JwksFetcher::create(Upstream::ClusterManager& cm) {
return JwksFetcherPtr(new JwksFetcherImpl(cm));
Comment thread
nickrmc83 marked this conversation as resolved.
Outdated
}
} // namespace Common
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
69 changes: 69 additions & 0 deletions source/extensions/filters/http/common/jwks_fetcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#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
Copy Markdown
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
Copy Markdown
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 a single JWKS and should not be re-used to fetch further instances.
*/
class JwksFetcher {
Comment thread
nickrmc83 marked this conversation as resolved.
public:
class JwksReceiver {
public:
enum class Failure {
unknown,
Comment thread
nickrmc83 marked this conversation as resolved.
Outdated
network,
invalid_jwks,
};

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 inflight request.
*/
virtual void cancel() PURE;

/*
* Retrieve a JWKS resource from a remote HTTP host.

Copy link
Copy Markdown
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
Copy Markdown
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

* @param uri the uri to retrieve the jwks from.
Comment thread
nickrmc83 marked this conversation as resolved.
*/
virtual void fetch(const ::envoy::api::v2::core::HttpUri& uri, JwksReceiver* receiver) PURE;
Comment thread
nickrmc83 marked this conversation as resolved.
Outdated

/*
* 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