Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 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
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
36 changes: 36 additions & 0 deletions source/extensions/formatter/req_without_query/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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",
],
)
24 changes: 24 additions & 0 deletions source/extensions/formatter/req_without_query/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "extensions/formatter/req_without_query/config.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<ProtobufWkt::StringValue>();
}

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());
Comment thread
tsaarni marked this conversation as resolved.
Outdated
}

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

str = str.substr(0, max_length.value());
}
Comment thread
tsaarni marked this conversation as 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,

@dio dio Apr 19, 2021

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.

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

Copy link
Copy Markdown
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
28 changes: 28 additions & 0 deletions test/extensions/formatter/req_without_query/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_package",
)
load(
"//test/extensions:extensions_build_system.bzl",
"envoy_extension_cc_test",
)

licenses(["notice"]) # Apache 2

envoy_package()

envoy_extension_cc_test(
name = "req_without_query_test",
srcs = ["req_without_query_test.cc"],
extension_name = "envoy.formatter.req_without_query",
deps = [
"//source/common/formatter:substitution_formatter_lib",
"//source/common/json:json_loader_lib",
"//source/extensions/formatter/req_without_query:config",
"//source/extensions/formatter/req_without_query:req_without_query_lib",
"//test/mocks/server:factory_context_mocks",
"//test/mocks/stream_info:stream_info_mocks",
"//test/test_common:test_runtime_lib",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "envoy/config/core/v3/substitution_format_string.pb.validate.h"

#include "common/formatter/substitution_format_string.h"

#include "test/mocks/server/factory_context.h"
#include "test/mocks/stream_info/mocks.h"
#include "test/test_common/utility.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace Envoy {
namespace Extensions {
namespace Formatter {

class ReqWithoutQueryTest : public ::testing::Test {
public:
Http::TestRequestHeaderMapImpl request_headers_{
{":method", "GET"},
{":path", "/request/path?secret=parameter"},
{"x-envoy-original-path", "/original/path?secret=parameter"}};
Http::TestResponseHeaderMapImpl response_headers_;
Http::TestResponseTrailerMapImpl response_trailers_;
StreamInfo::MockStreamInfo stream_info_;
std::string body_;

envoy::config::core::v3::SubstitutionFormatString config_;
NiceMock<Server::Configuration::MockFactoryContext> context_;
};

TEST_F(ReqWithoutQueryTest, TestStripQueryString) {
const std::string yaml = R"EOF(
text_format_source:
inline_string: "%REQ_WITHOUT_QUERY(:PATH)%"
formatters:
- name: envoy.formatter.req_without_query
typed_config:
"@type": type.googleapis.com/google.protobuf.StringValue
Comment thread
tsaarni marked this conversation as resolved.
Outdated
)EOF";
TestUtility::loadFromYaml(yaml, config_);

auto formatter =
::Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_.api());
Comment thread
tsaarni marked this conversation as resolved.
Outdated
EXPECT_EQ("/request/path", formatter->format(request_headers_, response_headers_,
response_trailers_, stream_info_, body_));
}

TEST_F(ReqWithoutQueryTest, TestSelectMainHeader) {

const std::string yaml = R"EOF(
text_format_source:
inline_string: "%REQ_WITHOUT_QUERY(X-ENVOY-ORIGINAL-PATH?:PATH)%"
formatters:
- name: envoy.formatter.req_without_query
typed_config:
"@type": type.googleapis.com/google.protobuf.StringValue
)EOF";
TestUtility::loadFromYaml(yaml, config_);

auto formatter =
::Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_.api());
EXPECT_EQ("/original/path", formatter->format(request_headers_, response_headers_,
response_trailers_, stream_info_, body_));
}

TEST_F(ReqWithoutQueryTest, TestSelectAlternativeHeader) {

const std::string yaml = R"EOF(
text_format_source:
inline_string: "%REQ_WITHOUT_QUERY(X-NON-EXISTING-HEADER?:PATH)%"
formatters:
- name: envoy.formatter.req_without_query
typed_config:
"@type": type.googleapis.com/google.protobuf.StringValue
)EOF";
TestUtility::loadFromYaml(yaml, config_);

auto formatter =
::Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_.api());
EXPECT_EQ("/request/path", formatter->format(request_headers_, response_headers_,
response_trailers_, stream_info_, body_));
}

TEST_F(ReqWithoutQueryTest, TestTruncateHeader) {

const std::string yaml = R"EOF(
text_format_source:
inline_string: "%REQ_WITHOUT_QUERY(:PATH):5%"
formatters:
- name: envoy.formatter.req_without_query
typed_config:
"@type": type.googleapis.com/google.protobuf.StringValue
)EOF";
TestUtility::loadFromYaml(yaml, config_);

auto formatter =
::Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_.api());
EXPECT_EQ("/requ", formatter->format(request_headers_, response_headers_, response_trailers_,
stream_info_, body_));
}

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