Skip to content
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
10 changes: 10 additions & 0 deletions docs/root/configuration/observability/access_log/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://en.cppreference.com/w/cpp/io/manip/put_time>`_.
See :ref:`START_TIME <config_access_log_format_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 <https://en.cppreference.com/w/cpp/io/manip/put_time>`_.
See :ref:`START_TIME <config_access_log_format_start_time>` for additional format specifiers and examples.

%HOSTNAME%
The system hostname.

Expand Down
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ New Features
* config: added ability to flush stats when the admin's :ref:`/stats endpoint <operations_admin_interface_stats>` is hit instead of on a timer via :ref:`stats_flush_on_admin <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.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 <envoy_v3_api_field_config.core.v3.SubstitutionFormatString.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% <config_access_log_format_downstream_peer_cert_v_start>` and :ref:`%DOWNSTREAM_PEER_CERT_V_END% <config_access_log_format_downstream_peer_cert_v_end>`, similar to :ref:`%START_TIME% <config_access_log_format_start_time>`.
* grpc: implemented header value syntax support when defining :ref:`initial metadata <envoy_v3_api_field_config.core.v3.GrpcService.initial_metadata>` for gRPC-based `ext_authz` :ref:`HTTP <envoy_v3_api_field_extensions.filters.http.ext_authz.v3.ExtAuthz.grpc_service>` and :ref:`network <envoy_v3_api_field_extensions.filters.network.ext_authz.v3.ExtAuthz.grpc_service>` filters, and :ref:`ratelimit <envoy_v3_api_field_config.ratelimit.v3.RateLimitServiceConfig.grpc_service>` filters.
* grpc-json: added support for configuring :ref:`unescaping behavior <envoy_v3_api_field_extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder.url_unescape_spec>` for path components.
* hds: added support for delta updates in the :ref:`HealthCheckSpecifier <envoy_v3_api_msg_service.health.v3.HealthCheckSpecifier>`, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS.
Expand Down
111 changes: 68 additions & 43 deletions source/common/formatter/substitution_formatter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ void truncate(std::string& str, absl::optional<uint32_t> 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"); }
Expand Down Expand Up @@ -370,18 +370,11 @@ std::vector<FormatterProviderPtr> SubstitutionFormatParser::parse(const std::str
formatters.push_back(
std::make_unique<FilterStateFormatter>(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<size_t>())});
Expand Down Expand Up @@ -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<StreamInfoSslConnectionInfoFieldExtractor>(
[](const Ssl::ConnectionInfo& connection_info) {
absl::optional<SystemTime> time = connection_info.validFromPeerCertificate();
absl::optional<std::string> 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<StreamInfoSslConnectionInfoFieldExtractor>(
[](const Ssl::ConnectionInfo& connection_info) {
absl::optional<SystemTime> time = connection_info.expirationPeerCertificate();
absl::optional<std::string> 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<StreamInfoStringFieldExtractor>(
[](const StreamInfo::StreamInfo& stream_info) {
Expand Down Expand Up @@ -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<SystemTimeFormatter::TimeFieldExtractor>(
[](const StreamInfo::StreamInfo& stream_info) -> absl::optional<SystemTime> {
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<SystemTimeFormatter::TimeFieldExtractor>(
[](const StreamInfo::StreamInfo& stream_info) -> absl::optional<SystemTime> {
const auto connection_info = stream_info.downstreamSslConnection();
return connection_info != nullptr ? connection_info->validFromPeerCertificate()
: absl::optional<SystemTime>();
})) {}

// 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<SystemTimeFormatter::TimeFieldExtractor>(
[](const StreamInfo::StreamInfo& stream_info) -> absl::optional<SystemTime> {
const auto connection_info = stream_info.downstreamSslConnection();
return connection_info != nullptr ? connection_info->expirationPeerCertificate()
: absl::optional<SystemTime>();
})) {}

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<std::string> StartTimeFormatter::format(const Http::RequestHeaderMap&,
const Http::ResponseHeaderMap&,
const Http::ResponseTrailerMap&,
const StreamInfo::StreamInfo& stream_info,
absl::string_view) const {
absl::optional<std::string> 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 {
Expand Down
48 changes: 44 additions & 4 deletions source/common/formatter/substitution_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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};
};

/**
Expand Down Expand Up @@ -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<absl::optional<SystemTime>(const StreamInfo::StreamInfo& stream_info)>;
using TimeFieldExtractorPtr = std::unique_ptr<TimeFieldExtractor>;

SystemTimeFormatter(const std::string& format, TimeFieldExtractorPtr f);

// FormatterProvider
absl::optional<std::string> format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&,
Expand All @@ -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
Expand Down
Loading