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
27 changes: 25 additions & 2 deletions api/envoy/data/accesslog/v2/accesslog.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ message AccessLogCommon {
// This field is the local/destination address on which the request from the user was received.
envoy.api.v2.core.Address downstream_local_address = 3;

// [#not-implemented-hide:]
// If the connection is secure,S this field will contain TLS properties.
TLSProperties tls_properties = 4;

Expand Down Expand Up @@ -210,19 +209,21 @@ message ResponseFlags {
bool stream_idle_timeout = 17;
}

// [#not-implemented-hide:]
// Properties of a negotiated TLS connection.
message TLSProperties {
// [#not-implemented-hide:]
enum TLSVersion {
VERSION_UNSPECIFIED = 0;
TLSv1 = 1;
TLSv1_1 = 2;
TLSv1_2 = 3;
TLSv1_3 = 4;
}
// [#not-implemented-hide:]
// Version of TLS that was negotiated.
TLSVersion tls_version = 1;

// [#not-implemented-hide:]
// TLS cipher suite negotiated during handshake. The value is a
// four-digit hex code defined by the IANA TLS Cipher Suite Registry
// (e.g. ``009C`` for ``TLS_RSA_WITH_AES_128_GCM_SHA256``).
Expand All @@ -232,6 +233,28 @@ message TLSProperties {

// SNI hostname from handshake.
string tls_sni_hostname = 3;

message CertificateProperties {
message SubjectAltName {
oneof san {
string uri = 1;
// [#not-implemented-hide:]
string dns = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, i'll remove

Copy link
Member

Choose a reason for hiding this comment

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

A not implemented annotation is also fine.

}
}

// SANs present in the certificate.
repeated SubjectAltName subject_alt_name = 1;

// The subject field of the certificate.
string subject = 2;
}

// Properties of the local certificate used to negotiate TLS.
CertificateProperties local_certificate_properties = 4;

// Properties of the peer certificate used to negotiate TLS.
CertificateProperties peer_certificate_properties = 5;
}

message HTTPRequestProperties {
Expand Down
25 changes: 25 additions & 0 deletions docs/root/configuration/access_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,28 @@ The following command operators are supported:
TCP
String value set on ssl connection socket for Server Name Indication (SNI)

%DOWNSTREAM_LOCAL_URI_SAN%
Copy link
Member

Choose a reason for hiding this comment

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

Not directly actionable in this PR perhaps, but need to think about how to structurally express those field in logging format and keep file access log feature parity with gRPC. We'll likely have upstream TLS set for this as well, and other properties which is defined in accesslog.proto.

Some random idea would be making the proto representation as first class, and have some expression in file access log to represent extractor from there.

HTTP
The URIs present in the SAN of the local certificate used to establish the downstream TLS connection.
TCP
The URIs present in the SAN of the local certificate used to establish the downstream TLS connection.

%DOWNSTREAM_PEER_URI_SAN%
HTTP
The URIs present in the SAN of the peer certificate used to establish the downstream TLS connection.
TCP
The URIs present in the SAN of the peer certificate used to establish the downstream TLS connection.

%DOWNSTREAM_LOCAL_SUBJECT%
HTTP
The subject present in the local certificate used to establish the downstream TLS connection.
TCP
The subject present in the local certificate used to establish the downstream TLS connection.

%DOWNSTREAM_PEER_SUBJECT%
HTTP
The subject present in the peer certificate used to establish the downstream TLS connection.
TCP
The subject present in the peer certificate used to establish the downstream TLS connection.


1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Version history
* access log: added a new flag for stream idle timeout.
* access log: added a new field for upstream transport failure reason in :ref:`file access logger<config_access_log_format_upstream_transport_failure_reason>` and
:ref:`gRPC access logger<envoy_api_field_data.accesslog.v2.AccessLogCommon.upstream_transport_failure_reason>` for HTTP access logs.
* access log: added new fields for downstream x509 information (URI sans and subject) to file and gRPC access logger.
* admin: the admin server can now be accessed via HTTP/2 (prior knowledge).
* buffer: fix vulnerabilities when allocation fails.
* build: releases are built with GCC-7 and linked with LLD.
Expand Down
3 changes: 2 additions & 1 deletion include/envoy/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ class Connection : public Event::DeferredDeletable, public FilterManager {
/**
* @return the const SSL connection data if this is an SSL connection, or nullptr if it is not.
*/
virtual const Ssl::Connection* ssl() const PURE;
// TODO(snowp): Remove this in favor of StreamInfo::downstreamSslConnection.
virtual const Ssl::ConnectionInfo* ssl() const PURE;
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to remove this interface from Connection and move into StreamInfo? If the change will be too heavy just leave a TODO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add a TODO, it seems to require changing a bunch of functions that pass the connection around


/**
* @return requested server name (e.g. SNI in TLS), if any.
Expand Down
2 changes: 1 addition & 1 deletion include/envoy/network/transport_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class TransportSocket {
/**
* @return the const SSL connection data if this is an SSL connection, or nullptr if it is not.
*/
virtual const Ssl::Connection* ssl() const PURE;
virtual const Ssl::ConnectionInfo* ssl() const PURE;
};

typedef std::unique_ptr<TransportSocket> TransportSocketPtr;
Expand Down
14 changes: 7 additions & 7 deletions include/envoy/ssl/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ namespace Ssl {
/**
* Base connection interface for all SSL connections.
*/
class Connection {
class ConnectionInfo {
Copy link
Member

Choose a reason for hiding this comment

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

+1

public:
virtual ~Connection() {}
virtual ~ConnectionInfo() {}

/**
* @return bool whether the peer certificate is presented.
**/
virtual bool peerCertificatePresented() const PURE;

/**
* @return std::string the URI in the SAN field of the local certificate. Returns "" if there is
* @return std::string the URIs in the SAN field of the local certificate. Returns {} if there is
* no local certificate, or no SAN field, or no URI.
**/
virtual std::string uriSanLocalCertificate() const PURE;
virtual std::vector<std::string> uriSanLocalCertificate() const PURE;

/**
* @return std::string the subject field of the local certificate in RFC 2253 format. Returns ""
Expand All @@ -54,10 +54,10 @@ class Connection {
virtual std::string subjectPeerCertificate() const PURE;

/**
* @return std::string the URI in the SAN field of the peer certificate. Returns "" if there is no
* peer certificate, or no SAN field, or no URI.
* @return std::string the URIs in the SAN field of the peer certificate. Returns {} if there is
*no peer certificate, or no SAN field, or no URI.
**/
virtual std::string uriSanPeerCertificate() const PURE;
virtual std::vector<std::string> uriSanPeerCertificate() const PURE;

/**
* @return std::string the URL-encoded PEM-encoded representation of the peer certificate. Returns
Expand Down
1 change: 1 addition & 0 deletions include/envoy/stream_info/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ envoy_cc_library(
":filter_state_interface",
"//include/envoy/common:time_interface",
"//include/envoy/http:protocol_interface",
"//include/envoy/ssl:connection_interface",
"//include/envoy/upstream:host_description_interface",
"//source/common/common:assert_lib",
"//source/common/protobuf",
Expand Down
12 changes: 12 additions & 0 deletions include/envoy/stream_info/stream_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "envoy/common/pure.h"
#include "envoy/common/time.h"
#include "envoy/http/protocol.h"
#include "envoy/ssl/connection.h"
#include "envoy/stream_info/filter_state.h"
#include "envoy/upstream/host_description.h"

Expand Down Expand Up @@ -326,6 +327,17 @@ class StreamInfo {
*/
virtual const Network::Address::InstanceConstSharedPtr& downstreamRemoteAddress() const PURE;

/**
* @param connection_info sets the downstream ssl connection.
*/
virtual void setDownstreamSslConnection(const Ssl::ConnectionInfo* ssl_connection_info) PURE;

/**
* @return the downstream SSL connection. This will be nullptr if the downstream
* connection does not use SSL.
*/
virtual const Ssl::ConnectionInfo* downstreamSslConnection() const PURE;

/**
* @return const Router::RouteEntry* Get the route entry selected for this request. Note: this
* will be nullptr if no route was selected.
Expand Down
41 changes: 41 additions & 0 deletions source/common/access_log/access_log_formatter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ namespace AccessLog {

static const std::string UnspecifiedValueString = "-";

namespace {

// Helper that handles the case when the ConnectionInfo is missing or if the desired value is
// empty.
StreamInfoFormatter::FieldExtractor sslConnectionInfoStringExtractor(
std::function<std::string(const Ssl::ConnectionInfo& connection_info)> string_extractor) {
return [string_extractor](const StreamInfo::StreamInfo& stream_info) {
if (stream_info.downstreamSslConnection() == nullptr) {
return UnspecifiedValueString;
}

const auto value = string_extractor(*stream_info.downstreamSslConnection());
if (value.empty()) {
return UnspecifiedValueString;
} else {
return value;
}
};
}
} // namespace

const std::string AccessLogFormatUtils::DEFAULT_FORMAT =
"[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" "
"%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% "
Expand Down Expand Up @@ -350,6 +371,26 @@ StreamInfoFormatter::StreamInfoFormatter(const std::string& field_name) {
return UnspecifiedValueString;
}
};
} else if (field_name == "DOWNSTREAM_PEER_URI_SAN") {
field_extractor_ =
sslConnectionInfoStringExtractor([](const Ssl::ConnectionInfo& connection_info) {
return absl::StrJoin(connection_info.uriSanPeerCertificate(), ",");
});
} else if (field_name == "DOWNSTREAM_LOCAL_URI_SAN") {
field_extractor_ =
sslConnectionInfoStringExtractor([](const Ssl::ConnectionInfo& connection_info) {
return absl::StrJoin(connection_info.uriSanLocalCertificate(), ",");
});
} else if (field_name == "DOWNSTREAM_PEER_SUBJECT") {
field_extractor_ =
sslConnectionInfoStringExtractor([](const Ssl::ConnectionInfo& connection_info) {
return connection_info.subjectPeerCertificate();
});
} else if (field_name == "DOWNSTREAM_LOCAL_SUBJECT") {
field_extractor_ =
sslConnectionInfoStringExtractor([](const Ssl::ConnectionInfo& connection_info) {
return connection_info.subjectLocalCertificate();
});
} else if (field_name == "UPSTREAM_TRANSPORT_FAILURE_REASON") {
field_extractor_ = [](const StreamInfo::StreamInfo& stream_info) {
if (!stream_info.upstreamTransportFailureReason().empty()) {
Expand Down
4 changes: 3 additions & 1 deletion source/common/access_log/access_log_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,10 @@ class StreamInfoFormatter : public FormatterProvider {
std::string format(const Http::HeaderMap&, const Http::HeaderMap&, const Http::HeaderMap&,
const StreamInfo::StreamInfo& stream_info) const override;

using FieldExtractor = std::function<std::string(const StreamInfo::StreamInfo&)>;

private:
std::function<std::string(const StreamInfo::StreamInfo&)> field_extractor_;
FieldExtractor field_extractor_;
};

/**
Expand Down
2 changes: 2 additions & 0 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect
stream_info_.setDownstreamRemoteAddress(
connection_manager_.read_callbacks_->connection().remoteAddress());

stream_info_.setDownstreamSslConnection(connection_manager_.read_callbacks_->connection().ssl());

if (connection_manager_.config_.streamIdleTimeout().count()) {
idle_timeout_ms_ = connection_manager_.config_.streamIdleTimeout();
stream_idle_timer_ = connection_manager_.read_callbacks_->connection().dispatcher().createTimer(
Expand Down
13 changes: 8 additions & 5 deletions source/common/http/conn_manager_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,9 @@ void ConnectionManagerUtility::mutateXfccRequestHeader(HeaderMap& request_header
// the XFCC header.
if (config.forwardClientCert() == ForwardClientCertType::AppendForward ||
config.forwardClientCert() == ForwardClientCertType::SanitizeSet) {
const std::string uri_san_local_cert = connection.ssl()->uriSanLocalCertificate();
if (!uri_san_local_cert.empty()) {
client_cert_details.push_back("By=" + uri_san_local_cert);
const auto uri_sans_local_cert = connection.ssl()->uriSanLocalCertificate();
if (!uri_sans_local_cert.empty()) {
client_cert_details.push_back("By=" + uri_sans_local_cert[0]);
}
const std::string cert_digest = connection.ssl()->sha256PeerCertificateDigest();
if (!cert_digest.empty()) {
Expand All @@ -301,10 +301,13 @@ void ConnectionManagerUtility::mutateXfccRequestHeader(HeaderMap& request_header
client_cert_details.push_back("Subject=\"" + connection.ssl()->subjectPeerCertificate() +
"\"");
break;
case ClientCertDetailsType::URI:
case ClientCertDetailsType::URI: {
// The "URI" key still exists even if the URI is empty.
client_cert_details.push_back("URI=" + connection.ssl()->uriSanPeerCertificate());
const auto sans = connection.ssl()->uriSanPeerCertificate();
const auto& uri_san = sans.empty() ? "" : sans[0];
client_cert_details.push_back("URI=" + uri_san);
break;
}
case ClientCertDetailsType::DNS: {
const std::vector<std::string> dns_sans = connection.ssl()->dnsSansPeerCertificate();
if (!dns_sans.empty()) {
Expand Down
2 changes: 1 addition & 1 deletion source/common/network/connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class ConnectionImpl : public virtual Connection,
return socket_->localAddress();
}
void setConnectionStats(const ConnectionStats& stats) override;
const Ssl::Connection* ssl() const override { return transport_socket_->ssl(); }
const Ssl::ConnectionInfo* ssl() const override { return transport_socket_->ssl(); }
State state() const override;
void write(Buffer::Instance& data, bool end_stream) override;
void setBufferLimits(uint32_t limit) override;
Expand Down
2 changes: 1 addition & 1 deletion source/common/network/raw_buffer_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class RawBufferSocket : public TransportSocket, protected Logger::Loggable<Logge
void onConnected() override;
IoResult doRead(Buffer::Instance& buffer) override;
IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override;
const Ssl::Connection* ssl() const override { return nullptr; }
const Ssl::ConnectionInfo* ssl() const override { return nullptr; }

private:
TransportSocketCallbacks* callbacks_{};
Expand Down
9 changes: 9 additions & 0 deletions source/common/stream_info/stream_info_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ struct StreamInfoImpl : public StreamInfo {
return downstream_remote_address_;
}

void setDownstreamSslConnection(const Ssl::ConnectionInfo* connection_info) override {
downstream_ssl_info_ = connection_info;
Copy link
Member

Choose a reason for hiding this comment

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

I'm slightly worried that StreamInfoImpl might outlive Ssl::ConnectionInfo, will that be the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I can tell it seems like Ssl::ConnectionInfo/Network::Connection comes from the FilterManagerImpl which should outlive the Filters that the StreamInfoImpl belongs to. Do you think it would be useful to expose the ConnectionInfo as a shared ptr to avoid potential lifetime issues in the future?

Copy link
Member

Choose a reason for hiding this comment

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

No I think it is fine to leave as is.

}

const Ssl::ConnectionInfo* downstreamSslConnection() const override {
return downstream_ssl_info_;
}

const Router::RouteEntry* routeEntry() const override { return route_entry_; }

envoy::api::v2::core::Metadata& dynamicMetadata() override { return metadata_; };
Expand Down Expand Up @@ -211,6 +219,7 @@ struct StreamInfoImpl : public StreamInfo {
Network::Address::InstanceConstSharedPtr downstream_local_address_;
Network::Address::InstanceConstSharedPtr downstream_direct_remote_address_;
Network::Address::InstanceConstSharedPtr downstream_remote_address_;
const Ssl::ConnectionInfo* downstream_ssl_info_;
std::string requested_server_name_;
UpstreamTiming upstream_timing_;
std::string upstream_transport_failure_reason_;
Expand Down
1 change: 1 addition & 0 deletions source/common/tcp_proxy/tcp_proxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ void Filter::initialize(Network::ReadFilterCallbacks& callbacks, bool set_connec
read_callbacks_->connection().enableHalfClose(true);
getStreamInfo().setDownstreamLocalAddress(read_callbacks_->connection().localAddress());
getStreamInfo().setDownstreamRemoteAddress(read_callbacks_->connection().remoteAddress());
getStreamInfo().setDownstreamSslConnection(read_callbacks_->connection().ssl());

// Need to disable reads so that we don't write to an upstream that might fail
// in onData(). This will get re-enabled when the upstream connection is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ void HttpGrpcAccessLog::log(const Http::HeaderMap* request_headers,

// Common log properties.
// TODO(mattklein123): Populate sample_rate field.
// TODO(mattklein123): Populate tls_properties field.
auto* common_properties = log_entry->mutable_common_properties();

if (stream_info.downstreamRemoteAddress() != nullptr) {
Expand All @@ -200,6 +199,28 @@ void HttpGrpcAccessLog::log(const Http::HeaderMap* request_headers,
*stream_info.downstreamLocalAddress(),
*common_properties->mutable_downstream_local_address());
}
if (stream_info.downstreamSslConnection() != nullptr) {
auto* tls_properties = common_properties->mutable_tls_properties();

tls_properties->set_tls_sni_hostname(stream_info.requestedServerName());

auto* local_properties = tls_properties->mutable_local_certificate_properties();
for (const auto& uri_san : stream_info.downstreamSslConnection()->uriSanLocalCertificate()) {
auto* local_san = local_properties->add_subject_alt_name();
local_san->set_uri(uri_san);
}
local_properties->set_subject(stream_info.downstreamSslConnection()->subjectLocalCertificate());

auto* peer_properties = tls_properties->mutable_peer_certificate_properties();
for (const auto& uri_san : stream_info.downstreamSslConnection()->uriSanPeerCertificate()) {
auto* peer_san = peer_properties->add_subject_alt_name();
peer_san->set_uri(uri_san);
}

peer_properties->set_subject(stream_info.downstreamSslConnection()->subjectPeerCertificate());

// TODO(snowp): Populate remaining tls_properties fields.
}
common_properties->mutable_start_time()->MergeFrom(
Protobuf::util::TimeUtil::NanosecondsToTimestamp(
std::chrono::duration_cast<std::chrono::nanoseconds>(
Expand Down
Loading