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

ext_authz: add metadata_context to ext_authz filter #7818

Merged
merged 7 commits into from
Aug 20, 2019
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
14 changes: 14 additions & 0 deletions api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ message ExtAuthz {
// Sets the HTTP status that is returned to the client when there is a network error between the
// filter and the authorization server. The default status is HTTP 403 Forbidden.
envoy.type.HttpStatus status_on_error = 7;

// Specifies a list of metadata namespaces whose values, if present, will be passed to the
htuch marked this conversation as resolved.
Show resolved Hide resolved
// ext_authz service as an opaque *protobuf::Struct*.
//
// For example, if the *jwt_authn* filter is used and :ref:`payload_in_metadata
// <envoy_api_field_config.filter.http.jwt_authn.v2alpha.JwtProvider.payload_in_metadata>` is set,
// then the following will pass the jwt payload to the authorization server.
//
// .. code-block:: yaml
//
// metadata_context_namespaces:
// - envoy.filters.http.jwt_authn
//
repeated string metadata_context_namespaces = 8;
}

// Configuration for buffering the request data.
Expand Down
4 changes: 4 additions & 0 deletions api/envoy/service/auth/v2/attribute_context.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ option java_multiple_files = true;
option java_package = "io.envoyproxy.envoy.service.auth.v2";

import "envoy/api/v2/core/address.proto";
import "envoy/api/v2/core/base.proto";

import "google/protobuf/timestamp.proto";
import "gogoproto/gogo.proto";
Expand Down Expand Up @@ -135,6 +136,9 @@ message AttributeContext {
// information to the auth server without modifying the proto definition. It maps to the
// internal opaque context in the filter chain.
map<string, string> context_extensions = 10;

// Dynamic metadata associated with the request.
envoy.api.v2.core.Metadata metadata_context = 11;
Copy link
Member

Choose a reason for hiding this comment

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

@envoyproxy/api-shepherds API LGTM

}

// The following items are left out of this proto
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Version history
* config: async data access for local and remote data source.
* config: changed the default value of :ref:`initial_fetch_timeout <envoy_api_field_core.ConfigSource.initial_fetch_timeout>` from 0s to 15s. This is a change in behaviour in the sense that Envoy will move to the next initialization phase, even if the first config is not delivered in 15s. Refer to :ref:`initialization process <arch_overview_initialization>` for more details.
* config: added stat :ref:`init_fetch_timeout <config_cluster_manager_cds>`.
* ext_authz: added :ref:`configurable ability <envoy_api_field_config.filter.http.ext_authz.v2.ExtAuthz.metadata_context_namespaces>` to send dynamic metadata to the `ext_authz` service.
* fault: added overrides for default runtime keys in :ref:`HTTPFault <envoy_api_msg_config.filter.http.fault.v2.HTTPFault>` filter.
* grpc: added :ref:`AWS IAM grpc credentials extension <envoy_api_file_envoy/config/grpc_credential/v2alpha/aws_iam.proto>` for AWS-managed xDS.
* grpc-json: added support for :ref:`ignoring unknown query parameters<envoy_api_field_config.filter.http.transcoder.v2.GrpcJsonTranscoder.ignore_unknown_query_parameters>`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ void CheckRequestUtils::createHttpCheck(
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers,
Protobuf::Map<std::string, std::string>&& context_extensions,
envoy::api::v2::core::Metadata&& metadata_context,
envoy::service::auth::v2::CheckRequest& request, uint64_t max_request_bytes) {

auto attrs = request.mutable_attributes();
Expand All @@ -158,6 +159,7 @@ void CheckRequestUtils::createHttpCheck(

// Fill in the context extensions:
(*attrs->mutable_context_extensions()) = std::move(context_extensions);
(*attrs->mutable_metadata_context()) = std::move(metadata_context);
}

void CheckRequestUtils::createTcpCheck(const Network::ReadFilterCallbacks* callbacks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class CheckRequestUtils {
static void createHttpCheck(const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers,
Protobuf::Map<std::string, std::string>&& context_extensions,
envoy::api::v2::core::Metadata&& metadata_context,
envoy::service::auth::v2::CheckRequest& request,
uint64_t max_request_bytes);

Expand Down
15 changes: 13 additions & 2 deletions source/extensions/filters/http/ext_authz/ext_authz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,20 @@ void Filter::initiateCall(const Http::HeaderMap& headers) {
if (maybe_merged_per_route_config) {
context_extensions = maybe_merged_per_route_config.value().takeContextExtensions();
}

dio marked this conversation as resolved.
Show resolved Hide resolved
// If metadata_context_namespaces is specified, pass matching metadata to the ext_authz service
envoy::api::v2::core::Metadata metadata_context;
const auto& request_metadata = callbacks_->streamInfo().dynamicMetadata().filter_metadata();
for (const auto& context_key : config_->metadataContextNamespaces()) {
const auto& metadata_it = request_metadata.find(context_key);
if (metadata_it != request_metadata.end()) {
(*metadata_context.mutable_filter_metadata())[metadata_it->first] = metadata_it->second;
}
}

Filters::Common::ExtAuthz::CheckRequestUtils::createHttpCheck(
callbacks_, headers, std::move(context_extensions), check_request_,
config_->maxRequestBytes());
callbacks_, headers, std::move(context_extensions), std::move(metadata_context),
check_request_, config_->maxRequestBytes());

ENVOY_STREAM_LOG(trace, "ext_authz filter calling authorization server", *callbacks_);
state_ = State::Calling;
Expand Down
9 changes: 9 additions & 0 deletions source/extensions/filters/http/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class FilterConfig {
max_request_bytes_(config.with_request_body().max_request_bytes()),
status_on_error_(toErrorCode(config.status_on_error().code())), local_info_(local_info),
scope_(scope), runtime_(runtime), http_context_(http_context), pool_(scope.symbolTable()),
metadata_context_namespaces_(config.metadata_context_namespaces().begin(),
config.metadata_context_namespaces().end()),
ext_authz_ok_(pool_.add("ext_authz.ok")), ext_authz_denied_(pool_.add("ext_authz.denied")),
ext_authz_error_(pool_.add("ext_authz.error")),
ext_authz_failure_mode_allowed_(pool_.add("ext_authz.failure_mode_allowed")) {}
Expand Down Expand Up @@ -75,6 +77,10 @@ class FilterConfig {
scope.counterFromStatName(name).inc();
}

const std::vector<std::string>& metadataContextNamespaces() {
return metadata_context_namespaces_;
}

private:
static Http::Code toErrorCode(uint64_t status) {
const auto code = static_cast<Http::Code>(status);
Expand All @@ -93,8 +99,11 @@ class FilterConfig {
Stats::Scope& scope_;
Runtime::Loader& runtime_;
Http::Context& http_context_;

Stats::StatNamePool pool_;

const std::vector<std::string> metadata_context_namespaces_;

public:
const Stats::StatName ext_authz_ok_;
const Stats::StatName ext_authz_denied_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttp) {

ExpectBasicHttp();
CheckRequestUtils::createHttpCheck(&callbacks_, request_headers,
Protobuf::Map<std::string, std::string>(), request_, size);
Protobuf::Map<std::string, std::string>(),
envoy::api::v2::core::Metadata(), request_, size);
ASSERT_EQ(size, request_.attributes().request().http().body().size());
EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body());
EXPECT_EQ(request_.attributes().request().http().headers().end(),
Expand All @@ -102,7 +103,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithPartialBody) {

ExpectBasicHttp();
CheckRequestUtils::createHttpCheck(&callbacks_, headers_,
Protobuf::Map<std::string, std::string>(), request_, size);
Protobuf::Map<std::string, std::string>(),
envoy::api::v2::core::Metadata(), request_, size);
ASSERT_EQ(size, request_.attributes().request().http().body().size());
EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body());
EXPECT_EQ("true", request_.attributes().request().http().headers().at(
Expand All @@ -116,8 +118,8 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBody) {

ExpectBasicHttp();
CheckRequestUtils::createHttpCheck(&callbacks_, headers_,
Protobuf::Map<std::string, std::string>(), request_,
buffer_->length());
Protobuf::Map<std::string, std::string>(),
envoy::api::v2::core::Metadata(), request_, buffer_->length());
ASSERT_EQ(buffer_->length(), request_.attributes().request().http().body().size());
EXPECT_EQ(buffer_->toString().substr(0, buffer_->length()),
request_.attributes().request().http().body());
Expand Down Expand Up @@ -146,13 +148,24 @@ TEST_F(CheckRequestUtilsTest, CheckAttrContextPeer) {
Protobuf::Map<std::string, std::string> context_extensions;
context_extensions["key"] = "value";

envoy::api::v2::core::Metadata metadata_context;
auto metadata_val = MessageUtil::keyValueStruct("foo", "bar");
(*metadata_context.mutable_filter_metadata())["meta.key"] = metadata_val;

CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, std::move(context_extensions),
request, false);
std::move(metadata_context), request, false);

EXPECT_EQ("source", request.attributes().source().principal());
EXPECT_EQ("destination", request.attributes().destination().principal());
EXPECT_EQ("foo", request.attributes().source().service());
EXPECT_EQ("value", request.attributes().context_extensions().at("key"));
EXPECT_EQ("bar", request.attributes()
.metadata_context()
.filter_metadata()
.at("meta.key")
.fields()
.at("foo")
.string_value());
}

} // namespace
Expand Down
62 changes: 62 additions & 0 deletions test/extensions/filters/http/ext_authz/ext_authz_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,68 @@ TEST_F(HttpFilterTest, NoClearCacheRouteDeniedResponse) {
EXPECT_EQ("ext_authz_denied", filter_callbacks_.details_);
}

// Verifies that specified metadata is passed along in the check request
TEST_F(HttpFilterTest, MetadataContext) {
initialize(R"EOF(
grpc_service:
envoy_grpc:
cluster_name: "ext_authz_server"
metadata_context_namespaces:
- jazz.sax
- rock.guitar
- hiphop.drums
)EOF");

const std::string yaml = R"EOF(
filter_metadata:
jazz.sax:
coltrane: john
parker: charlie
jazz.piano:
monk: thelonious
hancock: herbie
rock.guitar:
hendrix: jimi
richards: keith
)EOF";

envoy::api::v2::core::Metadata metadata;
TestUtility::loadFromYaml(yaml, metadata);
ON_CALL(filter_callbacks_.stream_info_, dynamicMetadata()).WillByDefault(ReturnRef(metadata));

prepareCheck();

envoy::service::auth::v2::CheckRequest check_request;
EXPECT_CALL(*client_, check(_, _, _))
.WillOnce(WithArgs<1>(Invoke([&](const envoy::service::auth::v2::CheckRequest& check_param)
-> void { check_request = check_param; })));

filter_->decodeHeaders(request_headers_, false);
Http::MetadataMap metadata_map{{"metadata", "metadata"}};
EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter_->decodeMetadata(metadata_map));

EXPECT_EQ("john", check_request.attributes()
.metadata_context()
.filter_metadata()
.at("jazz.sax")
.fields()
.at("coltrane")
.string_value());

EXPECT_EQ("jimi", check_request.attributes()
.metadata_context()
.filter_metadata()
.at("rock.guitar")
.fields()
.at("hendrix")
.string_value());

EXPECT_EQ(0, check_request.attributes().metadata_context().filter_metadata().count("jazz.piano"));

EXPECT_EQ(0,
check_request.attributes().metadata_context().filter_metadata().count("hiphop.drums"));
}

// -------------------
// Parameterized Tests
// -------------------
Expand Down