diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index 7d4b402fbd617..9a00ac1a29d93 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -535,18 +535,28 @@ The following command operators are supported: TCP The client certificate in the URL-encoded PEM format used to establish the downstream TLS connection. +.. _config_access_log_format_downstream_peer_cert_v_start: + %DOWNSTREAM_PEER_CERT_V_START% HTTP The validity start date of the client certificate used to establish the downstream TLS connection. TCP The validity start date of the client certificate used to establish the downstream TLS connection. + DOWNSTREAM_PEER_CERT_V_START can be customized using a `format string `_. + See :ref:`START_TIME ` for additional format specifiers and examples. + +.. _config_access_log_format_downstream_peer_cert_v_end: + %DOWNSTREAM_PEER_CERT_V_END% HTTP The validity end date of the client certificate used to establish the downstream TLS connection. TCP The validity end date of the client certificate used to establish the downstream TLS connection. + DOWNSTREAM_PEER_CERT_V_END can be customized using a `format string `_. + See :ref:`START_TIME ` for additional format specifiers and examples. + %HOSTNAME% The system hostname. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 452cb21bcfcf5..3204d147ddaf7 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -67,6 +67,7 @@ New Features * config: added ability to flush stats when the admin's :ref:`/stats endpoint ` is hit instead of on a timer via :ref:`stats_flush_on_admin `. * config: added new runtime feature `envoy.features.enable_all_deprecated_features` that allows the use of all deprecated features. * formatter: added new :ref:`text_format_source ` field to support format strings both inline and from a file. +* formatter: added support for custom date formatting to :ref:`%DOWNSTREAM_PEER_CERT_V_START% ` and :ref:`%DOWNSTREAM_PEER_CERT_V_END% `, similar to :ref:`%START_TIME% `. * grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. * grpc-json: added support for configuring :ref:`unescaping behavior ` for path components. * hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. diff --git a/source/common/formatter/substitution_formatter.cc b/source/common/formatter/substitution_formatter.cc index fc6a92fa99956..d1351cf7ac80e 100644 --- a/source/common/formatter/substitution_formatter.cc +++ b/source/common/formatter/substitution_formatter.cc @@ -43,8 +43,8 @@ void truncate(std::string& str, absl::optional max_length) { str = str.substr(0, max_length.value()); } -// Matches newline pattern in a StartTimeFormatter format string. -const std::regex& getStartTimeNewlinePattern() { +// Matches newline pattern in a system time format string (e.g. start time) +const std::regex& getSystemTimeFormatNewlinePattern() { CONSTRUCT_ON_FIRST_USE(std::regex, "%[-_0^#]*[1-9]*(E|O)?n"); } const std::regex& getNewlinePattern() { CONSTRUCT_ON_FIRST_USE(std::regex, "\n"); } @@ -370,18 +370,11 @@ std::vector SubstitutionFormatParser::parse(const std::str formatters.push_back( std::make_unique(key, max_length, serialize_as_string)); } else if (absl::StartsWith(token, "START_TIME")) { - const size_t parameters_length = pos + StartTimeParamStart + 1; - const size_t parameters_end = command_end_position - parameters_length; - - const std::string args = token[StartTimeParamStart - 1] == '(' - ? token.substr(StartTimeParamStart, parameters_end) - : ""; - // Validate the input specifier here. The formatted string may be destined for a header, and - // should not contain invalid characters {NUL, LR, CF}. - if (std::regex_search(args, getStartTimeNewlinePattern())) { - throw EnvoyException("Invalid header configuration. Format string contains newline."); - } - formatters.emplace_back(FormatterProviderPtr{new StartTimeFormatter(args)}); + formatters.emplace_back(FormatterProviderPtr{new StartTimeFormatter(token)}); + } else if (absl::StartsWith(token, "DOWNSTREAM_PEER_CERT_V_START")) { + formatters.emplace_back(FormatterProviderPtr{new DownstreamPeerCertVStartFormatter(token)}); + } else if (absl::StartsWith(token, "DOWNSTREAM_PEER_CERT_V_END")) { + formatters.emplace_back(FormatterProviderPtr{new DownstreamPeerCertVEndFormatter(token)}); } else if (absl::StartsWith(token, "GRPC_STATUS")) { formatters.emplace_back(FormatterProviderPtr{ new GrpcStatusFormatter("grpc-status", "", absl::optional())}); @@ -770,26 +763,6 @@ StreamInfoFormatter::StreamInfoFormatter(const std::string& field_name) { [](const Ssl::ConnectionInfo& connection_info) { return connection_info.urlEncodedPemEncodedPeerCertificate(); }); - } else if (field_name == "DOWNSTREAM_PEER_CERT_V_START") { - field_extractor_ = std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - absl::optional time = connection_info.validFromPeerCertificate(); - absl::optional result; - if (time.has_value()) { - result = AccessLogDateTimeFormatter::fromTime(time.value()); - } - return result; - }); - } else if (field_name == "DOWNSTREAM_PEER_CERT_V_END") { - field_extractor_ = std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - absl::optional time = connection_info.expirationPeerCertificate(); - absl::optional result; - if (time.has_value()) { - result = AccessLogDateTimeFormatter::fromTime(time.value()); - } - return result; - }); } else if (field_name == "UPSTREAM_TRANSPORT_FAILURE_REASON") { field_extractor_ = std::make_unique( [](const StreamInfo::StreamInfo& stream_info) { @@ -1131,20 +1104,72 @@ ProtobufWkt::Value FilterStateFormatter::formatValue(const Http::RequestHeaderMa return val; } -StartTimeFormatter::StartTimeFormatter(const std::string& format) : date_formatter_(format) {} +// Given a token, extract the command string between parenthesis if it exists. +std::string SystemTimeFormatter::parseFormat(const std::string& token, size_t parameters_start) { + const size_t parameters_length = token.length() - (parameters_start + 1); + return token[parameters_start - 1] == '(' ? token.substr(parameters_start, parameters_length) + : ""; +} + +// A SystemTime formatter that extracts the startTime from StreamInfo. Must be provided +// an access log token that starts with `START_TIME`. +StartTimeFormatter::StartTimeFormatter(const std::string& token) + : SystemTimeFormatter( + parseFormat(token, sizeof("START_TIME(") - 1), + std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + return stream_info.startTime(); + })) {} + +// A SystemTime formatter that optionally extracts the start date from the downstream peer's +// certificate. Must be provided an access log token that starts with `DOWNSTREAM_PEER_CERT_V_START` +DownstreamPeerCertVStartFormatter::DownstreamPeerCertVStartFormatter(const std::string& token) + : SystemTimeFormatter( + parseFormat(token, sizeof("DOWNSTREAM_PEER_CERT_V_START(") - 1), + std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + const auto connection_info = stream_info.downstreamSslConnection(); + return connection_info != nullptr ? connection_info->validFromPeerCertificate() + : absl::optional(); + })) {} + +// A SystemTime formatter that optionally extracts the end date from the downstream peer's +// certificate. Must be provided an access log token that starts with `DOWNSTREAM_PEER_CERT_V_END` +DownstreamPeerCertVEndFormatter::DownstreamPeerCertVEndFormatter(const std::string& token) + : SystemTimeFormatter( + parseFormat(token, sizeof("DOWNSTREAM_PEER_CERT_V_END(") - 1), + std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + const auto connection_info = stream_info.downstreamSslConnection(); + return connection_info != nullptr ? connection_info->expirationPeerCertificate() + : absl::optional(); + })) {} + +SystemTimeFormatter::SystemTimeFormatter(const std::string& format, TimeFieldExtractorPtr f) + : date_formatter_(format), time_field_extractor_(std::move(f)) { + // Validate the input specifier here. The formatted string may be destined for a header, and + // should not contain invalid characters {NUL, LR, CF}. + if (std::regex_search(format, getSystemTimeFormatNewlinePattern())) { + throw EnvoyException("Invalid header configuration. Format string contains newline."); + } +} -absl::optional StartTimeFormatter::format(const Http::RequestHeaderMap&, - const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, - absl::string_view) const { +absl::optional SystemTimeFormatter::format(const Http::RequestHeaderMap&, + const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo& stream_info, + absl::string_view) const { + const auto time_field = (*time_field_extractor_)(stream_info); + if (!time_field.has_value()) { + return absl::nullopt; + } if (date_formatter_.formatString().empty()) { - return AccessLogDateTimeFormatter::fromTime(stream_info.startTime()); + return AccessLogDateTimeFormatter::fromTime(time_field.value()); } - return date_formatter_.fromTime(stream_info.startTime()); + return date_formatter_.fromTime(time_field.value()); } -ProtobufWkt::Value StartTimeFormatter::formatValue( +ProtobufWkt::Value SystemTimeFormatter::formatValue( const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo& stream_info, absl::string_view local_reply_body) const { diff --git a/source/common/formatter/substitution_formatter.h b/source/common/formatter/substitution_formatter.h index 66ac8f83e2834..dc0d39689e9bc 100644 --- a/source/common/formatter/substitution_formatter.h +++ b/source/common/formatter/substitution_formatter.h @@ -64,7 +64,6 @@ class SubstitutionFormatParser { static const size_t ReqParamStart{sizeof("REQ(") - 1}; static const size_t RespParamStart{sizeof("RESP(") - 1}; static const size_t TrailParamStart{sizeof("TRAILER(") - 1}; - static const size_t StartTimeParamStart{sizeof("START_TIME(") - 1}; }; /** @@ -379,11 +378,15 @@ class FilterStateFormatter : public FormatterProvider { }; /** - * FormatterProvider for request start time from StreamInfo. + * Base FormatterProvider for system times from StreamInfo. */ -class StartTimeFormatter : public FormatterProvider { +class SystemTimeFormatter : public FormatterProvider { public: - StartTimeFormatter(const std::string& format); + using TimeFieldExtractor = + std::function(const StreamInfo::StreamInfo& stream_info)>; + using TimeFieldExtractorPtr = std::unique_ptr; + + SystemTimeFormatter(const std::string& format, TimeFieldExtractorPtr f); // FormatterProvider absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, @@ -393,8 +396,45 @@ class StartTimeFormatter : public FormatterProvider { const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, absl::string_view) const override; +protected: + // Given an access log token, attempt to parse out the format string between parenthesis. + // + // @param token The access log token, e.g. `START_TIME` or `START_TIME(...)` + // @param parameters_start The index of the first character where the parameters parenthesis would + // begin if it exists. Must not be out of bounds of `token` or its NUL + // char. + // @return The format string between parenthesis, or an empty string if none exists. + static std::string parseFormat(const std::string& token, size_t parameters_start); + private: const Envoy::DateFormatter date_formatter_; + const TimeFieldExtractorPtr time_field_extractor_; +}; + +/** + * SystemTimeFormatter (FormatterProvider) for request start time from StreamInfo. + */ +class StartTimeFormatter : public SystemTimeFormatter { +public: + StartTimeFormatter(const std::string& format); +}; + +/** + * SystemTimeFormatter (FormatterProvider) for downstream cert start time from the StreamInfo's + * ConnectionInfo. + */ +class DownstreamPeerCertVStartFormatter : public SystemTimeFormatter { +public: + DownstreamPeerCertVStartFormatter(const std::string& format); +}; + +/** + * SystemTimeFormatter (FormatterProvider) for downstream cert end time from the StreamInfo's + * ConnectionInfo. + */ +class DownstreamPeerCertVEndFormatter : public SystemTimeFormatter { +public: + DownstreamPeerCertVEndFormatter(const std::string& format); }; } // namespace Formatter diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 81a0ba4f7f365..9e83c2d4fe95f 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -972,71 +972,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { stream_info, body), ProtoEq(ValueUtil::nullValue())); } - { - StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_START"); - auto connection_info = std::make_shared(); - absl::Time abslStartTime = - TestUtility::parseTime("Dec 18 01:50:34 2018 GMT", "%b %e %H:%M:%S %Y GMT"); - SystemTime startTime = absl::ToChronoTime(abslStartTime); - EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(startTime)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); - EXPECT_EQ("2018-12-18T01:50:34.000Z", - upstream_format.format(request_headers, response_headers, response_trailers, - stream_info, body)); - } - { - StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_START"); - auto connection_info = std::make_shared(); - EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(absl::nullopt)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); - EXPECT_EQ(absl::nullopt, upstream_format.format(request_headers, response_headers, - response_trailers, stream_info, body)); - EXPECT_THAT(upstream_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body), - ProtoEq(ValueUtil::nullValue())); - } - { - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(nullptr)); - StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_START"); - EXPECT_EQ(absl::nullopt, upstream_format.format(request_headers, response_headers, - response_trailers, stream_info, body)); - EXPECT_THAT(upstream_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body), - ProtoEq(ValueUtil::nullValue())); - } - { - StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_END"); - auto connection_info = std::make_shared(); - absl::Time abslEndTime = - TestUtility::parseTime("Dec 17 01:50:34 2020 GMT", "%b %e %H:%M:%S %Y GMT"); - SystemTime endTime = absl::ToChronoTime(abslEndTime); - EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(endTime)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); - EXPECT_EQ("2020-12-17T01:50:34.000Z", - upstream_format.format(request_headers, response_headers, response_trailers, - stream_info, body)); - } - { - StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_END"); - auto connection_info = std::make_shared(); - EXPECT_CALL(*connection_info, expirationPeerCertificate()) - .WillRepeatedly(Return(absl::nullopt)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); - EXPECT_EQ(absl::nullopt, upstream_format.format(request_headers, response_headers, - response_trailers, stream_info, body)); - EXPECT_THAT(upstream_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body), - ProtoEq(ValueUtil::nullValue())); - } - { - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(nullptr)); - StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_END"); - EXPECT_EQ(absl::nullopt, upstream_format.format(request_headers, response_headers, - response_trailers, stream_info, body)); - EXPECT_THAT(upstream_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body), - ProtoEq(ValueUtil::nullValue())); - } { StreamInfoFormatter upstream_format("UPSTREAM_TRANSPORT_FAILURE_REASON"); std::string upstream_transport_failure_reason = "SSL error"; @@ -1456,6 +1391,119 @@ TEST(SubstitutionFormatterTest, FilterStateFormatter) { } } +TEST(SubstitutionFormatterTest, DownstreamPeerCertVStartFormatter) { + NiceMock stream_info; + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers; + Http::TestResponseTrailerMapImpl response_trailers; + std::string body; + + // No downstreamSslConnection + { + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(nullptr)); + DownstreamPeerCertVStartFormatter cert_start_formart("DOWNSTREAM_PEER_CERT_V_START(%Y/%m/%d)"); + EXPECT_EQ(absl::nullopt, cert_start_formart.format(request_headers, response_headers, + response_trailers, stream_info, body)); + EXPECT_THAT(cert_start_formart.formatValue(request_headers, response_headers, response_trailers, + stream_info, body), + ProtoEq(ValueUtil::nullValue())); + } + // No validFromPeerCertificate + { + DownstreamPeerCertVStartFormatter cert_start_formart("DOWNSTREAM_PEER_CERT_V_START(%Y/%m/%d)"); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(absl::nullopt)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + EXPECT_EQ(absl::nullopt, cert_start_formart.format(request_headers, response_headers, + response_trailers, stream_info, body)); + EXPECT_THAT(cert_start_formart.formatValue(request_headers, response_headers, response_trailers, + stream_info, body), + ProtoEq(ValueUtil::nullValue())); + } + // Default format string + { + DownstreamPeerCertVStartFormatter cert_start_format("DOWNSTREAM_PEER_CERT_V_START"); + auto connection_info = std::make_shared(); + time_t test_epoch = 1522280158; + SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); + EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(time)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), + cert_start_format.format(request_headers, response_headers, response_trailers, + stream_info, body)); + } + // Custom format string + { + DownstreamPeerCertVStartFormatter cert_start_format( + "DOWNSTREAM_PEER_CERT_V_START(%b %e %H:%M:%S %Y %Z)"); + auto connection_info = std::make_shared(); + time_t test_epoch = 1522280158; + SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); + EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(time)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + EXPECT_EQ("Mar 28 23:35:58 2018 UTC", + cert_start_format.format(request_headers, response_headers, response_trailers, + stream_info, body)); + } +} + +TEST(SubstitutionFormatterTest, DownstreamPeerCertVEndFormatter) { + NiceMock stream_info; + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers; + Http::TestResponseTrailerMapImpl response_trailers; + std::string body; + + // No downstreamSslConnection + { + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(nullptr)); + DownstreamPeerCertVEndFormatter cert_end_format("DOWNSTREAM_PEER_CERT_V_END(%Y/%m/%d)"); + EXPECT_EQ(absl::nullopt, cert_end_format.format(request_headers, response_headers, + response_trailers, stream_info, body)); + EXPECT_THAT(cert_end_format.formatValue(request_headers, response_headers, response_trailers, + stream_info, body), + ProtoEq(ValueUtil::nullValue())); + } + // No expirationPeerCertificate + { + DownstreamPeerCertVEndFormatter cert_end_format("DOWNSTREAM_PEER_CERT_V_END(%Y/%m/%d)"); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, expirationPeerCertificate()) + .WillRepeatedly(Return(absl::nullopt)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + EXPECT_EQ(absl::nullopt, cert_end_format.format(request_headers, response_headers, + response_trailers, stream_info, body)); + EXPECT_THAT(cert_end_format.formatValue(request_headers, response_headers, response_trailers, + stream_info, body), + ProtoEq(ValueUtil::nullValue())); + } + // Default format string + { + DownstreamPeerCertVEndFormatter cert_end_format("DOWNSTREAM_PEER_CERT_V_END"); + auto connection_info = std::make_shared(); + time_t test_epoch = 1522280158; + SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); + EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(time)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), + cert_end_format.format(request_headers, response_headers, response_trailers, + stream_info, body)); + } + // Custom format string + { + DownstreamPeerCertVEndFormatter cert_end_format( + "DOWNSTREAM_PEER_CERT_V_END(%b %e %H:%M:%S %Y %Z)"); + auto connection_info = std::make_shared(); + time_t test_epoch = 1522280158; + SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); + EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(time)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + EXPECT_EQ("Mar 28 23:35:58 2018 UTC", + cert_end_format.format(request_headers, response_headers, response_trailers, + stream_info, body)); + } +} + TEST(SubstitutionFormatterTest, StartTimeFormatter) { NiceMock stream_info; Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; @@ -1464,7 +1512,7 @@ TEST(SubstitutionFormatterTest, StartTimeFormatter) { std::string body; { - StartTimeFormatter start_time_format("%Y/%m/%d"); + StartTimeFormatter start_time_format("START_TIME(%Y/%m/%d)"); time_t test_epoch = 1522280158; SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(stream_info, startTime()).WillRepeatedly(Return(time)); @@ -1476,7 +1524,7 @@ TEST(SubstitutionFormatterTest, StartTimeFormatter) { } { - StartTimeFormatter start_time_format(""); + StartTimeFormatter start_time_format("START_TIME"); SystemTime time; EXPECT_CALL(stream_info, startTime()).WillRepeatedly(Return(time)); EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), @@ -2149,6 +2197,7 @@ TEST(SubstitutionFormatterTest, CompositeFormatterSuccess) { } { + // Various START_TIME formats const std::string format = "%START_TIME(%Y/%m/%d)%|%START_TIME(%s)%|%START_TIME(bad_format)%|" "%START_TIME%|%START_TIME(%f.%1f.%2f.%3f)%"; @@ -2163,6 +2212,46 @@ TEST(SubstitutionFormatterTest, CompositeFormatterSuccess) { formatter.format(request_header, response_header, response_trailer, stream_info, body)); } + { + // Various DOWNSTREAM_PEER_CERT_V_START formats (similar to START_TIME) + const std::string format = + "%DOWNSTREAM_PEER_CERT_V_START(%Y/%m/" + "%d)%|%DOWNSTREAM_PEER_CERT_V_START(%s)%|%DOWNSTREAM_PEER_CERT_V_START(bad_format)%|" + "%DOWNSTREAM_PEER_CERT_V_START%|%DOWNSTREAM_PEER_CERT_V_START(%f.%1f.%2f.%3f)%"; + + time_t expected_time_in_epoch = 1522280158; + auto connection_info = std::make_shared(); + SystemTime time = std::chrono::system_clock::from_time_t(expected_time_in_epoch); + EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(time)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + FormatterImpl formatter(format, false); + + EXPECT_EQ( + fmt::format("2018/03/28|{}|bad_format|2018-03-28T23:35:58.000Z|000000000.0.00.000", + expected_time_in_epoch), + formatter.format(request_header, response_header, response_trailer, stream_info, body)); + } + + { + // Various DOWNSTREAM_PEER_CERT_V_END formats (similar to START_TIME) + const std::string format = + "%DOWNSTREAM_PEER_CERT_V_END(%Y/%m/" + "%d)%|%DOWNSTREAM_PEER_CERT_V_END(%s)%|%DOWNSTREAM_PEER_CERT_V_END(bad_format)%|" + "%DOWNSTREAM_PEER_CERT_V_END%|%DOWNSTREAM_PEER_CERT_V_END(%f.%1f.%2f.%3f)%"; + + time_t expected_time_in_epoch = 1522280158; + auto connection_info = std::make_shared(); + SystemTime time = std::chrono::system_clock::from_time_t(expected_time_in_epoch); + EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(time)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); + FormatterImpl formatter(format, false); + + EXPECT_EQ( + fmt::format("2018/03/28|{}|bad_format|2018-03-28T23:35:58.000Z|000000000.0.00.000", + expected_time_in_epoch), + formatter.format(request_header, response_header, response_trailer, stream_info, body)); + } + { // This tests the beginning of time. const std::string format = "%START_TIME(%Y/%m/%d)%|%START_TIME(%s)%|%START_TIME(bad_format)%|"