Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature untrusted client certs #9172

Merged
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
17 changes: 16 additions & 1 deletion api/envoy/api/v2/auth/cert.proto
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,19 @@ message TlsSessionTicketKeys {
[(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true];
}

// [#next-free-field: 10]
// [#next-free-field: 11]
message CertificateValidationContext {
// Peer certificate verification mode.
enum TrustChainVerification {
// Perform default certificate verification (e.g., against CA / verification lists)
VERIFY_TRUST_CHAIN = 0;

// Connections where the certificate fails verification will be permitted.
// For HTTP connections, the result of certificate verification can be used in route matching. (
// see :ref:`validated <envoy_api_field_route.RouteMatch.TlsContextMatchOptions.validated>` ).
ACCEPT_UNTRUSTED = 1;
}

// TLS certificate data containing certificate authority certificates to use in verifying
// a presented peer certificate (e.g. server certificate for clusters or client certificate
// for listeners). If not specified and a peer certificate is presented it will not be
Expand Down Expand Up @@ -300,6 +311,10 @@ message CertificateValidationContext {

// If specified, Envoy will not reject expired certificates.
bool allow_expired_certificate = 8;

// Certificate trust chain verification mode.
TrustChainVerification trust_chain_verification = 10
[(validate.rules).enum = {defined_only: true}];
}

// TLS context shared by both client and server TLS contexts.
Expand Down
5 changes: 5 additions & 0 deletions api/envoy/api/v2/route/route_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,12 @@ message RouteMatch {

message TlsContextMatchOptions {
// If specified, the route will match against whether or not a certificate is presented.
// If not specified, certificate presentation status (true or false) will not be considered when route matching.
google.protobuf.BoolValue presented = 1;

// If specified, the route will match against whether or not a certificate is validated.
Copy link
Member

Choose a reason for hiding this comment

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

What is the default here? Can you clarify?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If not specified, then the validated status (true or false) will not be considered for route matching - it's the same behaviour as the 'presented' field above.

Copy link
Member

Choose a reason for hiding this comment

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

OK can you do me a favor and clarify the docs both here and above that no value means not considered at all, a set value checks that state, etc. Feel free to phrase however you want but it was a bit unclear to me from a quick doc read. Thank you!

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've updated it, let me know if it's ok. Thanks.

// If not specified, certificate validation status (true or false) will not be considered when route matching.
google.protobuf.BoolValue validated = 2;
}

reserved 5;
Expand Down
5 changes: 5 additions & 0 deletions api/envoy/config/route/v3/route_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,12 @@ message RouteMatch {
"envoy.api.v2.route.RouteMatch.TlsContextMatchOptions";

// If specified, the route will match against whether or not a certificate is presented.
// If not specified, certificate presentation status (true or false) will not be considered when route matching.
google.protobuf.BoolValue presented = 1;

// If specified, the route will match against whether or not a certificate is validated.
// If not specified, certificate validation status (true or false) will not be considered when route matching.
google.protobuf.BoolValue validated = 2;
}

reserved 5, 3;
Expand Down
17 changes: 16 additions & 1 deletion api/envoy/extensions/transport_sockets/tls/v3/cert.proto
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,22 @@ message TlsSessionTicketKeys {
[(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true];
}

// [#next-free-field: 10]
// [#next-free-field: 11]
message CertificateValidationContext {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.auth.CertificateValidationContext";

// Peer certificate verification mode.
enum TrustChainVerification {
// Perform default certificate verification (e.g., against CA / verification lists)
VERIFY_TRUST_CHAIN = 0;

// Connections where the certificate fails verification will be permitted.
// For HTTP connections, the result of certificate verification can be used in route matching. (
// see :ref:`validated <envoy_api_field_config.route.v3.RouteMatch.TlsContextMatchOptions.validated>` ).
ACCEPT_UNTRUSTED = 1;
}

reserved 4;

reserved "verify_subject_alt_name";
Expand Down Expand Up @@ -307,6 +318,10 @@ message CertificateValidationContext {

// If specified, Envoy will not reject expired certificates.
bool allow_expired_certificate = 8;

// Certificate trust chain verification mode.
TrustChainVerification trust_chain_verification = 10
[(validate.rules).enum = {defined_only: true}];
}

// TLS context shared by both client and server TLS contexts.
Expand Down
2 changes: 2 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Version history
* config: use type URL to select an extension whenever the config type URL (or its previous versions) uniquely identify a typed extension, see :ref:`extension configuration <config_overview_extension_configuration>`.
* http: fixing a bug in HTTP/1.0 responses where Connection: keep-alive was not appended for connections which were kept alive.
* retry: added a retry predicate that :ref:`rejects hosts based on metadata. <envoy_api_field_route.RetryPolicy.retry_host_predicate>`
* router: added the ability to match a route based on whether a downstream TLS connection certificate has been
Copy link
Member

Choose a reason for hiding this comment

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

nit: alphabetical order

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

:ref:`validated <envoy_api_field_route.RouteMatch.TlsContextMatchOptions.validated>`.
* upstream: combined HTTP/1 and HTTP/2 connection pool code. This means that circuit breaker
limits for both requests and connections apply to both pool types. Also, HTTP/2 now has
the option to limit concurrent requests on a connection, and allow multiple draining
Expand Down
17 changes: 16 additions & 1 deletion generated_api_shadow/envoy/api/v2/auth/cert.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions include/envoy/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -530,11 +530,23 @@ class MetadataMatchCriteria {
filterMatchCriteria(const std::set<std::string>& names) const PURE;
};

/**
* Criterion that a route entry uses for matching TLS connection context.
*/
class TlsContextMatchCriteria {
public:
virtual ~TlsContextMatchCriteria() = default;

/**
* @return bool indicating whether the client presented credentials.
*/
virtual const absl::optional<bool>& presented() const PURE;

/**
* @return bool indicating whether the client credentials successfully validated against the TLS
* context validation context.
*/
virtual const absl::optional<bool>& validated() 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.

can you add comment to this class and methods?

};

using TlsContextMatchCriteriaConstPtr = std::unique_ptr<const TlsContextMatchCriteria>;
Expand Down
8 changes: 8 additions & 0 deletions include/envoy/ssl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ envoy_cc_library(
hdrs = ["certificate_validation_context_config.h"],
deps = [
"//source/common/common:matchers_lib",
"@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
"@envoy_api//envoy/type/matcher/v3:pkg_cc_proto",
],
)

envoy_cc_library(
name = "ssl_socket_extended_info_interface",
hdrs = ["ssl_socket_extended_info.h"],
deps = [
],
)
8 changes: 8 additions & 0 deletions include/envoy/ssl/certificate_validation_context_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <vector>

#include "envoy/common/pure.h"
#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
#include "envoy/type/matcher/v3/string.pb.h"

namespace Envoy {
Expand Down Expand Up @@ -61,6 +62,13 @@ class CertificateValidationContextConfig {
* @return whether to ignore expired certificates (both too new and too old).
*/
virtual bool allowExpiredCertificate() const PURE;

/**
* @return client certificate validation configuration.
*/
virtual envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext::
TrustChainVerification
trustChainVerification() const PURE;
};

using CertificateValidationContextConfigPtr = std::unique_ptr<CertificateValidationContextConfig>;
Expand Down
5 changes: 5 additions & 0 deletions include/envoy/ssl/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class ConnectionInfo {
**/
virtual bool peerCertificatePresented() const PURE;

/**
* @return bool whether the peer certificate was validated.
**/
virtual bool peerCertificateValidated() const PURE;

/**
* @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.
Expand Down
30 changes: 30 additions & 0 deletions include/envoy/ssl/ssl_socket_extended_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <memory>
#include <string>
#include <vector>

#include "envoy/common/pure.h"

namespace Envoy {
namespace Ssl {

enum class ClientValidationStatus { NotValidated, NoClientCertificate, Validated, Failed };

class SslExtendedSocketInfo {
public:
virtual ~SslExtendedSocketInfo() = default;

/**
* Set the peer certificate validation status.
**/
virtual void setCertificateValidationStatus(ClientValidationStatus validated) PURE;

/**
* @return ClientValidationStatus The peer certificate validation status.
**/
virtual ClientValidationStatus certificateValidationStatus() const PURE;
};

} // namespace Ssl
} // namespace Envoy
6 changes: 6 additions & 0 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ bool RouteEntryImplBase::evaluateTlsContextMatch(const StreamInfo::StreamInfo& s
matches &= criteria.presented().value() == peer_presented;
}

if (criteria.validated().has_value()) {
const bool peer_validated = stream_info.downstreamSslConnection() &&
stream_info.downstreamSslConnection()->peerCertificateValidated();
matches &= criteria.validated().value() == peer_validated;
}

return matches;
}

Expand Down
4 changes: 4 additions & 0 deletions source/common/router/tls_context_match_criteria_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ TlsContextMatchCriteriaImpl::TlsContextMatchCriteriaImpl(
if (options.has_presented()) {
presented_ = options.presented().value();
}

if (options.has_validated()) {
validated_ = options.validated().value();
}
}

} // namespace Router
Expand Down
2 changes: 2 additions & 0 deletions source/common/router/tls_context_match_criteria_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ class TlsContextMatchCriteriaImpl : public TlsContextMatchCriteria {
const envoy::config::route::v3::RouteMatch::TlsContextMatchOptions& options);

const absl::optional<bool>& presented() const override { return presented_; }
const absl::optional<bool>& validated() const override { return validated_; }

private:
absl::optional<bool> presented_;
absl::optional<bool> validated_;
};

} // namespace Router
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl(
config.verify_certificate_hash().end()),
verify_certificate_spki_list_(config.verify_certificate_spki().begin(),
config.verify_certificate_spki().end()),
allow_expired_certificate_(config.allow_expired_certificate()) {
allow_expired_certificate_(config.allow_expired_certificate()),
trust_chain_verification_(config.trust_chain_verification()) {
if (ca_cert_.empty()) {
if (!certificate_revocation_list_.empty()) {
throw EnvoyException(fmt::format("Failed to load CRL from {} without trusted CA",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class CertificateValidationContextConfigImpl : public CertificateValidationConte
return verify_certificate_spki_list_;
}
bool allowExpiredCertificate() const override { return allow_expired_certificate_; }
envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext::
TrustChainVerification
trustChainVerification() const override {
return trust_chain_verification_;
}

private:
const std::string ca_cert_;
Expand All @@ -49,6 +54,8 @@ class CertificateValidationContextConfigImpl : public CertificateValidationConte
const std::vector<std::string> verify_certificate_hash_list_;
const std::vector<std::string> verify_certificate_spki_list_;
const bool allow_expired_certificate_;
const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext::
TrustChainVerification trust_chain_verification_;
};

} // namespace Ssl
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/transport_sockets/tls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ envoy_cc_library(
":utility_lib",
"//include/envoy/network:connection_interface",
"//include/envoy/network:transport_socket_interface",
"//include/envoy/ssl:ssl_socket_extended_info_interface",
"//include/envoy/ssl/private_key:private_key_callbacks_interface",
"//include/envoy/ssl/private_key:private_key_interface",
"//include/envoy/stats:stats_macros",
Expand Down Expand Up @@ -96,6 +97,7 @@ envoy_cc_library(
"//include/envoy/ssl:context_config_interface",
"//include/envoy/ssl:context_interface",
"//include/envoy/ssl:context_manager_interface",
"//include/envoy/ssl:ssl_socket_extended_info_interface",
"//include/envoy/ssl/private_key:private_key_interface",
"//include/envoy/stats:stats_interface",
"//include/envoy/stats:stats_macros",
Expand Down
Loading