diff --git a/changelogs/current/new_features/attributes__added-peer-certificate-valid-cel.rst b/changelogs/current/new_features/attributes__added-peer-certificate-valid-cel.rst new file mode 100644 index 0000000000000..7cd79ae3fcf24 --- /dev/null +++ b/changelogs/current/new_features/attributes__added-peer-certificate-valid-cel.rst @@ -0,0 +1,4 @@ +Added the ``connection.peer_certificate_valid`` CEL attribute, a bool indicating whether the +downstream peer certificate was presented and validated. In optional mTLS setups using +``trust_chain_verification: ACCEPT_UNTRUSTED``, a certificate may be presented without being +validated. diff --git a/docs/root/intro/arch_overview/advanced/attributes.rst b/docs/root/intro/arch_overview/advanced/attributes.rst index 27a28b4db07ea..4041b548fee87 100644 --- a/docs/root/intro/arch_overview/advanced/attributes.rst +++ b/docs/root/intro/arch_overview/advanced/attributes.rst @@ -113,7 +113,7 @@ RBAC): destination.address, string, Downstream connection local address destination.port, int, Downstream connection local port connection.id, uint, Downstream connection ID - connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented + connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer certificate is presented connection.requested_server_name, string, Requested server name in the downstream TLS connection connection.tls_version, string, TLS version of the downstream TLS connection connection.subject_local_certificate, string, The subject field of the local certificate in the downstream TLS connection @@ -124,6 +124,7 @@ RBAC): connection.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the downstream TLS connection connection.sha256_peer_certificate_digest, string, SHA256 digest of the peer certificate in the downstream TLS connection if present connection.peer_certificate, string, PEM-encoded peer certificate in the downstream TLS connection if present + connection.peer_certificate_valid, bool, Indicates whether the peer certificate in the downstream TLS connection was presented and validated connection.transport_failure_reason, string, The transport failure reason e.g. certificate validation failed The following additional attributes are available upon the downstream connection termination: diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc index 37fad4180b363..17d58858b20b8 100644 --- a/source/extensions/filters/common/expr/context.cc +++ b/source/extensions/filters/common/expr/context.cc @@ -315,6 +315,14 @@ const ConnectionLookupValues& ConnectionLookupValues::get() { .sslConnection() ->peerCertificatePresented()); }}, + {PeerCertificateValid, + [](const ConnectionWrapper& wrapper) -> absl::optional { + return CelValue::CreateBool( + wrapper.info_.downstreamAddressProvider().sslConnection() != nullptr && + wrapper.info_.downstreamAddressProvider() + .sslConnection() + ->peerCertificateValidated()); + }}, {RequestedServerName, [](const ConnectionWrapper& wrapper) -> absl::optional { return CelValue::CreateStringView( diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index bca62b0f78254..d7643c5e69dd7 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -72,6 +72,7 @@ constexpr absl::string_view DNSSanLocalCertificate = "dns_san_local_certificate" constexpr absl::string_view DNSSanPeerCertificate = "dns_san_peer_certificate"; constexpr absl::string_view SHA256PeerCertificateDigest = "sha256_peer_certificate_digest"; constexpr absl::string_view PeerCertificate = "peer_certificate"; +constexpr absl::string_view PeerCertificateValid = "peer_certificate_valid"; constexpr absl::string_view DownstreamTransportFailureReason = "transport_failure_reason"; // Source properties diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index 97883d5a76b26..264c1775d8f3f 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -536,6 +536,32 @@ TEST(Context, ConnectionFallbackAttributes) { } } +TEST(Context, ConnectionPeerCertificateNotValidated) { + // Presented but not validated (e.g. ACCEPT_UNTRUSTED): mtls=true, peer_certificate_valid=false. + NiceMock info; + auto ssl_info = std::make_shared>(); + info.downstream_connection_info_provider_->setSslConnection(ssl_info); + EXPECT_CALL(*ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); + EXPECT_CALL(*ssl_info, peerCertificateValidated()).WillRepeatedly(Return(false)); + + Protobuf::Arena arena; + ConnectionWrapper connection(arena, info); + + { + auto value = connection[CelValue::CreateStringView(MTLS)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsBool()); + EXPECT_TRUE(value.value().BoolOrDie()); + } + + { + auto value = connection[CelValue::CreateStringView(PeerCertificateValid)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsBool()); + EXPECT_FALSE(value.value().BoolOrDie()); + } +} + TEST(Context, ConnectionAttributes) { NiceMock info; std::shared_ptr> cluster_info( @@ -585,6 +611,7 @@ TEST(Context, ConnectionAttributes) { EXPECT_CALL(*downstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); EXPECT_CALL(*upstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); + EXPECT_CALL(*downstream_ssl_info, peerCertificateValidated()).WillRepeatedly(Return(true)); EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address)); EXPECT_CALL(*upstream_host, locality()).WillRepeatedly(ReturnRef(upstream_locality)); @@ -721,6 +748,13 @@ TEST(Context, ConnectionAttributes) { EXPECT_TRUE(value.value().BoolOrDie()); } + { + auto value = connection[CelValue::CreateStringView(PeerCertificateValid)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsBool()); + EXPECT_TRUE(value.value().BoolOrDie()); + } + { auto value = connection[CelValue::CreateStringView(RequestedServerName)]; EXPECT_TRUE(value.has_value()); diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 56bd9ffef7088..ffd743ac64ed7 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -3833,6 +3833,8 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { proto_config_.mutable_request_attributes()->Add("connection.id"); // tests uint64 proto_config_.mutable_request_attributes()->Add( "connection.peer_certificate"); // tests string, not present without TLS + proto_config_.mutable_request_attributes()->Add( + "connection.peer_certificate_valid"); // tests bool, present and false without TLS proto_config_.mutable_request_attributes()->Add("response.code"); proto_config_.mutable_response_attributes()->Add("response.code"); // tests int64 proto_config_.mutable_response_attributes()->Add("response.code_details"); @@ -3859,8 +3861,11 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { EXPECT_TRUE(proto_struct.fields().at("connection.id").has_number_value()); // connection.peer_certificate is not present without TLS EXPECT_FALSE(proto_struct.fields().contains("connection.peer_certificate")); + // connection.peer_certificate_valid is present and false without TLS (like connection.mtls) + EXPECT_EQ(proto_struct.fields().at("connection.peer_certificate_valid").bool_value(), + false); // Make sure we did not include the attribute which was not yet available. - EXPECT_EQ(proto_struct.fields().size(), 6); + EXPECT_EQ(proto_struct.fields().size(), 7); EXPECT_FALSE(proto_struct.fields().contains("response.code")); // Make sure we are not including any data in the deprecated HttpHeaders.attributes.