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

formatter: print request header without query string #15711

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f5ab2e6
formatter: print request header without query string
tsaarni Mar 26, 2021
dd0d834
formatter: empty config proto for req_without_query
tsaarni Apr 16, 2021
ff3080c
formatter: documentation for req_without_query
tsaarni Apr 18, 2021
dfd49a5
utility: helper for getting path without request string
tsaarni Apr 22, 2021
dcfb46b
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Apr 22, 2021
d601684
formatter: improved test coverage for req_without_query
tsaarni Apr 24, 2021
c9218bd
formatter: 100% test coverage for req_without_query
tsaarni Apr 24, 2021
0e20de5
Merge branch 'master' into issue7583-path-without-query-string
tsaarni May 6, 2021
e9a62da
Merge branch 'master' into issue7583-path-without-query-string
tsaarni May 11, 2021
afc16f7
Merge branch 'master' into issue7583-path-without-query-string
tsaarni May 14, 2021
1572fde
Merge branch 'master' into issue7583-path-without-query-string
tsaarni May 25, 2021
d17be60
formatter: moved helper function to anoymous namespace
tsaarni May 25, 2021
436df70
Merge branch 'master' into issue7583-path-without-query-string
tsaarni May 31, 2021
bd7b0a2
formatter: updated after review
tsaarni May 31, 2021
190253e
formatter: fixed broken documentation
tsaarni Jun 1, 2021
6623659
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 1, 2021
e01865e
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 2, 2021
e9cb1bb
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 8, 2021
dd2eb36
formatter: updated with the new include paths
tsaarni Jun 8, 2021
c4e28c7
docs: updated version history with info about new formatter extension
tsaarni Jun 8, 2021
d3b709c
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 8, 2021
74088ea
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 8, 2021
d49a6f5
formatter: changed the security posture of req_without_query
tsaarni Jun 9, 2021
5db4589
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 9, 2021
d12ec1d
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 11, 2021
b16deb1
tools: add formatter extension category
tsaarni Jun 11, 2021
708c346
formatter: updated req_without_query tests
tsaarni Jun 11, 2021
91d717d
tools: fixed code formatting after adding new extension
tsaarni Jun 11, 2021
52677c7
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 11, 2021
4e42d63
Merge branch 'master' into issue7583-path-without-query-string
tsaarni Jun 14, 2021
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
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,5 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp
/*/extensions/filters/common/ext_authz @esmet @gsagula @dio
/*/extensions/filters/http/ext_authz @esmet @gsagula @dio
/*/extensions/filters/network/ext_authz @esmet @gsagula @dio
# Formatters
/*/extensions/formatter/req_without_query @dio @tsaarni
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ proto_library(
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/formatter/req_without_query/v3:pkg",
"//envoy/extensions/health_checkers/redis/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions api/envoy/config/core/v3/substitution_format_string.proto
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,6 @@ message SubstitutionFormatString {

// Specifies a collection of Formatter plugins that can be called from the access log configuration.
// See the formatters extensions documentation for details.
// [#extension-category: envoy.formatter]
repeated TypedExtensionConfig formatters = 6;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions api/envoy/extensions/formatter/req_without_query/v3/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")

licenses(["notice"]) # Apache 2

api_proto_package(
deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
syntax = "proto3";

package envoy.extensions.formatter.req_without_query.v3;

import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.extensions.formatter.req_without_query.v3";
option java_outer_classname = "ReqWithoutQueryProto";
option java_multiple_files = true;
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Formatter extension for printing request without query string]
// [#extension: envoy.formatter.req_without_query]

// ReqWithoutQuery formatter extension implements REQ_WITHOUT_QUERY command operator that
// works the same way as :ref:`REQ <config_access_log_format_req>` except that it will
// remove the query string. It is used to avoid logging any sensitive information into
// the access log.

// %REQ_WITHOUT_QUERY(X?Y):Z%
// An HTTP request header where X is the main HTTP header, Y is the alternative one, and Z is an
// optional parameter denoting string truncation up to Z characters long. The value is taken from
// the HTTP request header named X first and if it's not set, then request header Y is used. If
// none of the headers are present '-' symbol will be in the log.

// [#not-implemented-hide:]
tsaarni marked this conversation as resolved.
Show resolved Hide resolved
// Placeholder for future configuration for the ReqWithoutQuery formatter.
message ReqWithoutQuery {
}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ proto_library(
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/formatter/req_without_query/v3:pkg",
"//envoy/extensions/health_checkers/redis/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions bazel/envoy_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ EXTENSION_CATEGORIES = [
"envoy.filters.listener",
"envoy.filters.network",
"envoy.filters.udp_listener",
"envoy.formatter",
"envoy.grpc_credentials",
"envoy.guarddog_actions",
"envoy.health_checkers",
Expand Down
2 changes: 2 additions & 0 deletions docs/root/configuration/observability/access_log/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,8 @@ The following command operators are supported:
%DOWNSTREAM_LOCAL_PORT%
Similar to **%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%**, but only extracts the port portion of the **%DOWNSTREAM_LOCAL_ADDRESS%**

.. _config_access_log_format_req:

%REQ(X?Y):Z%
HTTP
An HTTP request header where X is the main HTTP header, Y is the alternative one, and Z is an
Expand Down
1 change: 1 addition & 0 deletions generated_api_shadow/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ proto_library(
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/formatter/req_without_query/v3:pkg",
"//envoy/extensions/health_checkers/redis/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ EXTENSIONS = {
#

"envoy.tls.cert_validator.spiffe": "//source/extensions/transport_sockets/tls/cert_validator/spiffe:config",

#
# Formatter
#

"envoy.formatter.req_without_query": "//source/extensions/formatter/req_without_query:config",
}

# These can be changed to ["//visibility:public"], for downstream builds which
Expand Down
37 changes: 37 additions & 0 deletions source/extensions/formatter/req_without_query/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_extension",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

# Access log formatter that strips query string from request path
# Public docs: docs/root/TODO(tsaarni)

envoy_cc_library(
name = "req_without_query_lib",
srcs = ["req_without_query.cc"],
hdrs = ["req_without_query.h"],
deps = [
"//source/common/formatter:substitution_formatter_lib",
"//source/common/protobuf:utility_lib",
],
)

envoy_cc_extension(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
category = "envoy.formatter",
security_posture = "unknown",
status = "alpha",
deps = [
"//include/envoy/registry",
"//source/extensions/formatter/req_without_query:req_without_query_lib",
"@envoy_api//envoy/extensions/formatter/req_without_query/v3:pkg_cc_proto",
],
)
26 changes: 26 additions & 0 deletions source/extensions/formatter/req_without_query/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "extensions/formatter/req_without_query/config.h"

#include "envoy/extensions/formatter/req_without_query/v3/req_without_query.pb.h"

#include "extensions/formatter/req_without_query/req_without_query.h"

namespace Envoy {
namespace Extensions {
namespace Formatter {

::Envoy::Formatter::CommandParserPtr
ReqWithoutQueryFactory::createCommandParserFromProto(const Protobuf::Message&) {
return std::make_unique<ReqWithoutQueryCommandParser>();
}

ProtobufTypes::MessagePtr ReqWithoutQueryFactory::createEmptyConfigProto() {
return std::make_unique<envoy::extensions::formatter::req_without_query::v3::ReqWithoutQuery>();
}

std::string ReqWithoutQueryFactory::name() const { return "envoy.formatter.req_without_query"; }

REGISTER_FACTORY(ReqWithoutQueryFactory, ReqWithoutQueryFactory::CommandParserFactory);

} // namespace Formatter
} // namespace Extensions
} // namespace Envoy
19 changes: 19 additions & 0 deletions source/extensions/formatter/req_without_query/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "common/formatter/substitution_formatter.h"

namespace Envoy {
namespace Extensions {
namespace Formatter {

class ReqWithoutQueryFactory : public ::Envoy::Formatter::CommandParserFactory {
public:
::Envoy::Formatter::CommandParserPtr
createCommandParserFromProto(const Protobuf::Message&) override;
ProtobufTypes::MessagePtr createEmptyConfigProto() override;
std::string name() const override;
};

} // namespace Formatter
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "extensions/formatter/req_without_query/req_without_query.h"

#include <string>

#include "common/protobuf/utility.h"

namespace Envoy {
namespace Extensions {
namespace Formatter {

void stripQueryString(std::string& path) {
const size_t query_pos = path.find('?');
path = std::string(path.data(), query_pos != path.npos ? query_pos : path.size());
tsaarni marked this conversation as resolved.
Show resolved Hide resolved
}

void truncate(std::string& str, absl::optional<uint32_t> max_length) {
if (!max_length) {
return;
}

str = str.substr(0, max_length.value());
}
tsaarni marked this conversation as resolved.
Show resolved Hide resolved

ReqWithoutQuery::ReqWithoutQuery(const std::string& main_header,
const std::string& alternative_header,
absl::optional<size_t> max_length)
: main_header_(main_header), alternative_header_(alternative_header), max_length_(max_length) {}

absl::optional<std::string> ReqWithoutQuery::format(const Http::RequestHeaderMap& request,
const Http::ResponseHeaderMap&,
const Http::ResponseTrailerMap&,
const StreamInfo::StreamInfo&,
absl::string_view) const {
const Http::HeaderEntry* header = findHeader(request);
if (!header) {
return absl::nullopt;
}

std::string val = std::string(header->value().getStringView());
stripQueryString(val);
truncate(val, max_length_);

return val;
}

ProtobufWkt::Value ReqWithoutQuery::formatValue(const Http::RequestHeaderMap& request,
const Http::ResponseHeaderMap&,
const Http::ResponseTrailerMap&,
const StreamInfo::StreamInfo&,
absl::string_view) const {
const Http::HeaderEntry* header = findHeader(request);
if (!header) {
return ValueUtil::nullValue();
}

std::string val = std::string(header->value().getStringView());
stripQueryString(val);
truncate(val, max_length_);
return ValueUtil::stringValue(val);
}

const Http::HeaderEntry* ReqWithoutQuery::findHeader(const Http::HeaderMap& headers) const {
const auto header = headers.get(main_header_);

if (header.empty() && !alternative_header_.get().empty()) {
const auto alternate_header = headers.get(alternative_header_);
// TODO(https://github.com/envoyproxy/envoy/issues/13454): Potentially log all header values.
return alternate_header.empty() ? nullptr : alternate_header[0];
}

return header.empty() ? nullptr : header[0];
}

::Envoy::Formatter::FormatterProviderPtr
ReqWithoutQueryCommandParser::parse(const std::string& token, size_t, size_t) const {
if (absl::StartsWith(token, "REQ_WITHOUT_QUERY(")) {
std::string main_header, alternative_header;
absl::optional<size_t> max_length;

Envoy::Formatter::SubstitutionFormatParser::parseCommandHeader(
token, ReqWithoutQueryParamStart, main_header, alternative_header, max_length);
return std::make_unique<ReqWithoutQuery>(main_header, alternative_header, max_length);
}

return nullptr;
}

} // namespace Formatter
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <string>

#include "envoy/config/typed_config.h"
#include "envoy/registry/registry.h"

#include "common/formatter/substitution_formatter.h"

namespace Envoy {
namespace Extensions {
namespace Formatter {

class ReqWithoutQuery : public ::Envoy::Formatter::FormatterProvider {
public:
ReqWithoutQuery(const std::string& main_header, const std::string& alternative_header,
absl::optional<size_t> max_length);

absl::optional<std::string> format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&,
const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&,
absl::string_view) const override;
ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&,
const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&,
absl::string_view) const override;

private:
const Http::HeaderEntry* findHeader(const Http::HeaderMap& headers) const;

Http::LowerCaseString main_header_;
Http::LowerCaseString alternative_header_;
absl::optional<size_t> max_length_;
};

class ReqWithoutQueryCommandParser : public ::Envoy::Formatter::CommandParser {
public:
ReqWithoutQueryCommandParser() = default;
::Envoy::Formatter::FormatterProviderPtr parse(const std::string& token, size_t,
Copy link
Member

@dio dio Apr 19, 2021

Choose a reason for hiding this comment

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

Simply FormatterProviderPtr doesn't work here? Sorry for nitpicking.

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above, it did not work...

size_t) const override;

private:
static const size_t ReqWithoutQueryParamStart{sizeof("REQ_WITHOUT_QUERY(") - 1};
};

} // namespace Formatter
} // namespace Extensions
} // namespace Envoy
Loading