Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f7ed72d
Defined GrpcStatusFilter proto
crockeo Jan 20, 2019
0923e1a
Added a function to convert from grpc name to Status::GrpcStatus
crockeo Jan 20, 2019
4cec844
Implemented GrpcStatusFilter
crockeo Jan 21, 2019
c9b7c1c
Added validation to GrpcStatusFilter
crockeo Jan 21, 2019
0f73b01
Implemented validation-related tests
crockeo Jan 21, 2019
6486b4d
Added tests to check that the filter blocks the correct traffic
crockeo Jan 21, 2019
d8fc6d4
Added documentation to GrpcStatusFilter
crockeo Jan 22, 2019
0de9b81
Finished testing on request headers, did heavy lifting to modify code…
crockeo Jan 22, 2019
188b155
Added explicit hash to statuses_ to compile correctly on GCC 5.4.0
crockeo Jan 22, 2019
2ebb7d2
Fixed issue with missing hole in HttpGrpcAccessLogTest frame
crockeo Jan 22, 2019
c06354d
Addressed PR comments, aside from testing request.
crockeo Jan 22, 2019
93f4a2e
Added a test to ensure the filter infers the correct gRPC status from…
crockeo Jan 23, 2019
8e5a3d1
Changed initializer list to uniform initialization, and changed tuple…
crockeo Jan 24, 2019
ce9a103
Addressed comment updates
crockeo Jan 24, 2019
e94e234
Added test to check that, when lacking a gRPC status or HTTP status, …
crockeo Jan 24, 2019
58bac8c
Fixed s/respone/response/
crockeo Jan 24, 2019
cab43ed
Added test to check status Grpc::Utility::nameToGrpcStatus validity
crockeo Jan 24, 2019
93eb881
Added option to exclude based on gRPC status code
crockeo Jan 24, 2019
a562461
Included check for response_headers
crockeo Jan 24, 2019
631b44c
Break for optimization!
crockeo Jan 24, 2019
a247343
Added test for exclude: false
crockeo Jan 24, 2019
f4eee7d
Changed validated strings to enum.
crockeo Jan 25, 2019
a38443f
More directly linked GrpcStatusFilter enum test to Status::GrpcStatus…
crockeo Jan 26, 2019
569a63a
Addressed comments
crockeo Jan 26, 2019
52829ad
Updated comments
crockeo Jan 29, 2019
af976c5
Merge remote-tracking branch 'upstream/master' into dev
crockeo Jan 30, 2019
33df38d
CI test post-merge without explicit hash
crockeo Jan 30, 2019
02da790
Removed GrpcStatusHash
crockeo Jan 30, 2019
571a226
Fixed the invalid code test to work correctly variably serialized errors
crockeo Jan 30, 2019
1ccf23f
Merge remote-tracking branch 'upstream/master' into dev
crockeo Jan 31, 2019
1cbe0be
Merge remote-tracking branch 'upstream/master' into dev
crockeo Feb 1, 2019
6ba655e
Added status validation to GrpcStatusFilter protobuf
crockeo Feb 1, 2019
033d899
Replaced custom expect fail with substring with EXPECT_FAIL_WITH_REGEX
crockeo Feb 1, 2019
6c9a4a3
Set up ClangFormat on the new machine and formatted
crockeo Feb 1, 2019
7589725
Merge remote-tracking branch 'upstream/master' into dev
crockeo Feb 1, 2019
c64da70
Removed unused imports
crockeo Feb 3, 2019
3fb87e8
Changed stringstream to fmtlib
crockeo Feb 3, 2019
5e0e44b
Merge remote-tracking branch 'upstream/master' into dev
crockeo Feb 5, 2019
968b011
Removed unused sstream include
crockeo Feb 7, 2019
0915542
Changed vector to array
crockeo Feb 12, 2019
7625146
Added release note to version history
crockeo Feb 12, 2019
99db72b
Merge branch 'master' of github.com:envoyproxy/envoy into dev
crockeo Feb 12, 2019
ad58a61
Fixed mac-specific error with -Wmissing-braces
crockeo Feb 12, 2019
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
34 changes: 34 additions & 0 deletions api/envoy/config/filter/accesslog/v2/accesslog.proto
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ message AccessLogFilter {

// Response flag filter.
ResponseFlagFilter response_flag_filter = 9;

// gRPC status filter.
GrpcStatusFilter grpc_status_filter = 10;
}
}

Expand Down Expand Up @@ -192,3 +195,34 @@ message ResponseFlagFilter {
]
}];
}

// Filters gRPC requests based on their response status. If a gRPC status is not provided, the
// filter will infer the status from the HTTP status code.
message GrpcStatusFilter {
enum Status {
OK = 0;
CANCELED = 1;
UNKNOWN = 2;
INVALID_ARGUMENT = 3;
DEADLINE_EXCEEDED = 4;
NOT_FOUND = 5;
ALREADY_EXISTS = 6;
PERMISSION_DENIED = 7;
RESOURCE_EXHAUSTED = 8;
FAILED_PRECONDITION = 9;
ABORTED = 10;
OUT_OF_RANGE = 11;
UNIMPLEMENTED = 12;
INTERNAL = 13;
UNAVAILABLE = 14;
DATA_LOSS = 15;
UNAUTHENTICATED = 16;
}

// Logs only responses that have any one of the gRPC statuses in this field.
repeated Status statuses = 1 [(validate.rules).repeated .items.enum.defined_only = true];

// If included and set to true, the filter will instead block all responses with a gRPC status or
// inferred gRPC status enumerated in statuses, and allow all other responses.
bool exclude = 2;
}
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Version history
1.10.0 (pending)
================
* access log: added a new flag for upstream retry count exceeded.
* access log: added a :ref:`gRPC filter <envoy_api_msg_config.filter.accesslog.v2.GrpcStatusFilter>` to allow filtering on gRPC status.
* access log: added a new flag for stream idle timeout.
* admin: the admin server can now be accessed via HTTP/2 (prior knowledge).
* buffer: fix vulnerabilities when allocation fails.
Expand Down
5 changes: 3 additions & 2 deletions include/envoy/access_log/access_log.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ class Filter {
* Evaluate whether an access log should be written based on request and response data.
* @return TRUE if the log should be written.
*/
virtual bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) PURE;
virtual bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) PURE;
};

typedef std::unique_ptr<Filter> FilterPtr;
Expand Down
2 changes: 2 additions & 0 deletions include/envoy/grpc/status.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Grpc {

class Status {
public:
// If this enum is changed, then the std::unordered_map in Envoy::Grpc::Utility::nameToGrpcStatus
// located at: //source/common/access_log/grpc/status.cc must also be changed.
enum GrpcStatus {
// The RPC completed successfully.
Ok = 0,
Expand Down
84 changes: 71 additions & 13 deletions source/common/access_log/access_log_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,33 @@ FilterFactory::fromProto(const envoy::config::filter::accesslog::v2::AccessLogFi
case envoy::config::filter::accesslog::v2::AccessLogFilter::kResponseFlagFilter:
MessageUtil::validate(config);
return FilterPtr{new ResponseFlagFilter(config.response_flag_filter())};
case envoy::config::filter::accesslog::v2::AccessLogFilter::kGrpcStatusFilter:
MessageUtil::validate(config);
return FilterPtr{new GrpcStatusFilter(config.grpc_status_filter())};
default:
NOT_REACHED_GCOVR_EXCL_LINE;
}
}

bool TraceableRequestFilter::evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) {
const Http::HeaderMap& request_headers,
const Http::HeaderMap&, const Http::HeaderMap&) {
Tracing::Decision decision = Tracing::HttpTracerUtility::isTracing(info, request_headers);

return decision.traced && decision.reason == Tracing::Reason::ServiceForced;
}

bool StatusCodeFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&) {
bool StatusCodeFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&,
const Http::HeaderMap&, const Http::HeaderMap&) {
if (!info.responseCode()) {
return compareAgainstValue(0ULL);
}

return compareAgainstValue(info.responseCode().value());
}

bool DurationFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&) {
bool DurationFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&,
const Http::HeaderMap&, const Http::HeaderMap&) {
absl::optional<std::chrono::nanoseconds> final = info.requestComplete();
ASSERT(final);

Expand All @@ -108,7 +114,8 @@ RuntimeFilter::RuntimeFilter(const envoy::config::filter::accesslog::v2::Runtime
percent_(config.percent_sampled()),
use_independent_randomness_(config.use_independent_randomness()) {}

bool RuntimeFilter::evaluate(const StreamInfo::StreamInfo&, const Http::HeaderMap& request_header) {
bool RuntimeFilter::evaluate(const StreamInfo::StreamInfo&, const Http::HeaderMap& request_header,
const Http::HeaderMap&, const Http::HeaderMap&) {
const Http::HeaderEntry* uuid = request_header.RequestId();
uint64_t random_value;
if (use_independent_randomness_ || uuid == nullptr ||
Expand Down Expand Up @@ -139,11 +146,12 @@ AndFilter::AndFilter(const envoy::config::filter::accesslog::v2::AndFilter& conf
Runtime::Loader& runtime, Runtime::RandomGenerator& random)
: OperatorFilter(config.filters(), runtime, random) {}

bool OrFilter::evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) {
bool OrFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) {
bool result = false;
for (auto& filter : filters_) {
result |= filter->evaluate(info, request_headers);
result |= filter->evaluate(info, request_headers, response_headers, response_trailers);

if (result) {
break;
Expand All @@ -153,11 +161,12 @@ bool OrFilter::evaluate(const StreamInfo::StreamInfo& info,
return result;
}

bool AndFilter::evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) {
bool AndFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) {
bool result = true;
for (auto& filter : filters_) {
result &= filter->evaluate(info, request_headers);
result &= filter->evaluate(info, request_headers, response_headers, response_trailers);

if (!result) {
break;
Expand All @@ -167,15 +176,17 @@ bool AndFilter::evaluate(const StreamInfo::StreamInfo& info,
return result;
}

bool NotHealthCheckFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&) {
bool NotHealthCheckFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&,
const Http::HeaderMap&, const Http::HeaderMap&) {
return !info.healthCheck();
}

HeaderFilter::HeaderFilter(const envoy::config::filter::accesslog::v2::HeaderFilter& config) {
header_data_.push_back(Http::HeaderUtility::HeaderData(config.header()));
}

bool HeaderFilter::evaluate(const StreamInfo::StreamInfo&, const Http::HeaderMap& request_headers) {
bool HeaderFilter::evaluate(const StreamInfo::StreamInfo&, const Http::HeaderMap& request_headers,
const Http::HeaderMap&, const Http::HeaderMap&) {
return Http::HeaderUtility::matchHeaders(request_headers, header_data_);
}

Expand All @@ -190,13 +201,60 @@ ResponseFlagFilter::ResponseFlagFilter(
}
}

bool ResponseFlagFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&) {
bool ResponseFlagFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&,
const Http::HeaderMap&, const Http::HeaderMap&) {
if (configured_flags_ != 0) {
return info.intersectResponseFlags(configured_flags_);
}
return info.hasAnyResponseFlag();
}

GrpcStatusFilter::GrpcStatusFilter(
const envoy::config::filter::accesslog::v2::GrpcStatusFilter& config) {
for (int i = 0; i < config.statuses_size(); i++) {
statuses_.insert(protoToGrpcStatus(config.statuses(i)));
}

exclude_ = config.exclude();
}

bool GrpcStatusFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap&,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) {
// The gRPC specification does not guarantee a gRPC status code will be returned from a gRPC
// request. When it is returned, it will be in the response trailers. With that said, Envoy will
// treat a trailers-only response as a headers-only response, so we have to check the following
// in order:
// 1. response_trailers gRPC status, if it exists.
// 2. response_headers gRPC status, if it exists.
// 3. Inferred from info HTTP status, if it exists.
//
// If none of those options exist, it will default to Grpc::Status::GrpcStatus::Unknown.
const std::array<absl::optional<Grpc::Status::GrpcStatus>, 3> optional_statuses = {{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldn't repro compile errors locally, but it looked like it was expecting braces around each individual object in the array. To do so I needed to add the double brace around the entire initializer list.

Let me know if there's a better way to do it.

{Grpc::Common::getGrpcStatus(response_trailers)},
{Grpc::Common::getGrpcStatus(response_headers)},
{info.responseCode() ? absl::optional<Grpc::Status::GrpcStatus>(
Grpc::Utility::httpToGrpcStatus(info.responseCode().value()))
: absl::nullopt},
}};

Grpc::Status::GrpcStatus status = Grpc::Status::GrpcStatus::Unknown;
for (const auto& optional_status : optional_statuses) {
if (optional_status.has_value()) {
status = optional_status.value();
break;
}
}

const bool found = statuses_.find(status) != statuses_.end();
return exclude_ ? !found : found;
}

Grpc::Status::GrpcStatus GrpcStatusFilter::protoToGrpcStatus(
envoy::config::filter::accesslog::v2::GrpcStatusFilter_Status status) const {
return static_cast<Grpc::Status::GrpcStatus>(status);
}

InstanceSharedPtr
AccessLogFactory::fromProto(const envoy::config::filter::accesslog::v2::AccessLog& config,
Server::Configuration::FactoryContext& context) {
Expand Down
73 changes: 55 additions & 18 deletions source/common/access_log/access_log_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

#include <cstdint>
#include <string>
#include <unordered_set>
#include <vector>

#include "envoy/access_log/access_log.h"
#include "envoy/config/filter/accesslog/v2/accesslog.pb.h"
#include "envoy/runtime/runtime.h"
#include "envoy/server/access_log_config.h"

#include "common/grpc/status.h"
#include "common/http/header_utility.h"
#include "common/protobuf/protobuf.h"

Expand Down Expand Up @@ -51,8 +53,9 @@ class StatusCodeFilter : public ComparisonFilter {
: ComparisonFilter(config.comparison(), runtime) {}

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;
};

/**
Expand All @@ -65,8 +68,9 @@ class DurationFilter : public ComparisonFilter {
: ComparisonFilter(config.comparison(), runtime) {}

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;
};

/**
Expand All @@ -91,8 +95,9 @@ class AndFilter : public OperatorFilter {
Runtime::RandomGenerator& random);

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;
};

/**
Expand All @@ -104,8 +109,9 @@ class OrFilter : public OperatorFilter {
Runtime::RandomGenerator& random);

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;
};

/**
Expand All @@ -116,8 +122,9 @@ class NotHealthCheckFilter : public Filter {
NotHealthCheckFilter() {}

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;
};

/**
Expand All @@ -126,8 +133,9 @@ class NotHealthCheckFilter : public Filter {
class TraceableRequestFilter : public Filter {
public:
// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;
};

/**
Expand All @@ -139,8 +147,9 @@ class RuntimeFilter : public Filter {
Runtime::Loader& runtime, Runtime::RandomGenerator& random);

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;

private:
Runtime::Loader& runtime_;
Expand All @@ -158,8 +167,9 @@ class HeaderFilter : public Filter {
HeaderFilter(const envoy::config::filter::accesslog::v2::HeaderFilter& config);

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;

private:
std::vector<Http::HeaderUtility::HeaderData> header_data_;
Expand All @@ -173,13 +183,40 @@ class ResponseFlagFilter : public Filter {
ResponseFlagFilter(const envoy::config::filter::accesslog::v2::ResponseFlagFilter& config);

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info,
const Http::HeaderMap& request_headers) override;
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;

private:
uint64_t configured_flags_{};
};

/**
* Filters requests that have a response with a gRPC status. Because the gRPC protocol does not
* guarantee a gRPC status code, if a gRPC status code is not available, then the filter will infer
* the gRPC status code from an HTTP status code if available.
*/
class GrpcStatusFilter : public Filter {
public:
GrpcStatusFilter(const envoy::config::filter::accesslog::v2::GrpcStatusFilter& config);

// AccessLog::Filter
bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::HeaderMap& response_trailers) override;

private:
std::unordered_set<Grpc::Status::GrpcStatus> statuses_;
bool exclude_;

/**
* Converts a Protobuf representation of a gRPC status into the equivalent code version of a gRPC
* status.
*/
Grpc::Status::GrpcStatus
protoToGrpcStatus(envoy::config::filter::accesslog::v2::GrpcStatusFilter_Status status) const;
};

/**
* Access log factory that reads the configuration from proto.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void FileAccessLog::log(const Http::HeaderMap* request_headers,
}

if (filter_) {
if (!filter_->evaluate(stream_info, *request_headers)) {
if (!filter_->evaluate(stream_info, *request_headers, *response_headers, *response_trailers)) {
return;
}
}
Expand Down
Loading