Skip to content

Commit

Permalink
Add custom formatting to downstream cert start and end dates
Browse files Browse the repository at this point in the history
  • Loading branch information
esmet committed Jan 7, 2021
1 parent a68e0c0 commit 6db470e
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 118 deletions.
127 changes: 80 additions & 47 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]*n");
}
const std::regex& getNewlinePattern() { CONSTRUCT_ON_FIRST_USE(std::regex, "\n"); }
Expand Down Expand Up @@ -318,18 +318,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 @@ -731,24 +724,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();
if (!time.has_value()) {
return EMPTY_STRING;
}
return AccessLogDateTimeFormatter::fromTime(time.value());
});
} 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();
if (!time.has_value()) {
return EMPTY_STRING;
}
return AccessLogDateTimeFormatter::fromTime(time.value());
});
} else if (field_name == "UPSTREAM_TRANSPORT_FAILURE_REASON") {
field_extractor_ = std::make_unique<StreamInfoOptionalStringFieldExtractor>(
[](const StreamInfo::StreamInfo& stream_info) {
Expand Down Expand Up @@ -1093,26 +1068,84 @@ 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)
: "";
}

std::string StartTimeFormatter::format(const Http::RequestHeaderMap&,
const Http::ResponseHeaderMap&,
const Http::ResponseTrailerMap&,
const StreamInfo::StreamInfo& stream_info,
absl::string_view) const {
// 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.");
}
}

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 UnspecifiedValueString;
}
if (date_formatter_.formatString().empty()) {
return AccessLogDateTimeFormatter::fromTime(stream_info.startTime());
} else {
return date_formatter_.fromTime(stream_info.startTime());
return AccessLogDateTimeFormatter::fromTime(time_field.value());
}
return date_formatter_.fromTime(time_field.value());
}

ProtobufWkt::Value StartTimeFormatter::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 {
return ValueUtil::stringValue(
format(request_headers, response_headers, response_trailers, stream_info, local_reply_body));
ProtobufWkt::Value SystemTimeFormatter::formatValue(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 unspecifiedValue();
}
if (date_formatter_.formatString().empty()) {
return ValueUtil::stringValue(AccessLogDateTimeFormatter::fromTime(time_field.value()));
}
return ValueUtil::stringValue(date_formatter_.fromTime(time_field.value()));
}

} // namespace Formatter
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 @@ -65,7 +65,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 @@ -337,11 +336,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
std::string format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&,
Expand All @@ -351,8 +354,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

0 comments on commit 6db470e

Please sign in to comment.