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

SNI-based cert selection during TLS handshake #22036

Merged
merged 1 commit into from
Nov 30, 2022
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
14 changes: 8 additions & 6 deletions api/envoy/extensions/transport_sockets/tls/v3/tls.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ message UpstreamTlsContext {
google.protobuf.UInt32Value max_session_keys = 4;
}

// [#next-free-field: 9]
// [#next-free-field: 10]
message DownstreamTlsContext {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.auth.DownstreamTlsContext";
Expand Down Expand Up @@ -123,6 +123,11 @@ message DownstreamTlsContext {
// an accompanying OCSP response or if the response expires at runtime.
// Defaults to LENIENT_STAPLING
OcspStaplePolicy ocsp_staple_policy = 8 [(validate.rules).enum = {defined_only: true}];

// Multiple certificates are allowed in Downstream transport socket to serve different SNI.
// If the client provides SNI but no such cert matched, it will decide to full scan certificates or not based on this config.
// Defaults to false. See more details in :ref:`Multiple TLS certificates <arch_overview_ssl_cert_select>`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see documentation on the behavior when this setting is false and there's no SNI match.

Copy link
Contributor Author

@LuyaoZhong LuyaoZhong Nov 29, 2022

Choose a reason for hiding this comment

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

I didn't document too many details here, and would like to redirect users to doc Multiple TLS certificates <arch_overview_ssl_cert_select>. Should we document the behavior here as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, redirecting to that page is fine, as long as it is documented somewhere.

google.protobuf.BoolValue full_scan_certs_on_sni_mismatch = 9;
}

// TLS key log configuration.
Expand Down Expand Up @@ -227,12 +232,9 @@ message CommonTlsContext {
// TLS protocol versions, cipher suites etc.
TlsParameters tls_params = 1;

// Only a single TLS certificate is supported in client contexts. In server contexts,
// :ref:`Multiple TLS certificates <arch_overview_ssl_cert_select>` can be associated with the
// same context to allow both RSA and ECDSA certificates.
//
// Only a single TLS certificate is supported in client contexts. In server contexts, the first
// RSA certificate is used for clients that only support RSA and the first ECDSA certificate is
// used for clients that support ECDSA.
// same context to allow both RSA and ECDSA certificates and support SNI-based selection.
//
// Only one of ``tls_certificates``, ``tls_certificate_sds_secret_configs``,
// and ``tls_certificate_provider_instance`` may be used.
Expand Down
7 changes: 6 additions & 1 deletion changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ removed_config_or_runtime:
removed ``envoy.reloadable_features.do_not_await_headers_on_upstream_timeout_to_emit_stats`` and legacy code paths.

new_features:
- area: tls
change: |
added support for SNI-based cert selection in tls downstream transport socket. Detailed documentation is available :ref:`cert selection<arch_overview_ssl_cert_select>`.
New config option :ref:`full_scan_certs_on_sni_mismatch <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.full_scan_certs_on_sni_mismatch>`
is introduced to disable or enable full scan when no cert matches to SNI, defaults to false.
New runtime flag ``envoy.reloadable_features.no_full_scan_certs_on_sni_mismatch`` can be used for override the default value.
- area: build
change: |
added an option ``--define=library_autolink=disabled`` to disable autolinking libraries.
Expand Down Expand Up @@ -184,7 +190,6 @@ new_features:
- area: http
change: |
added :ref:`append_x_forwarded_port <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.append_x_forwarded_port>` to append the ``x-forwarded-port`` header to HTTP upstream requests.

- area: ext_authz
change: |
added support to allowlist headers included in the check request to gRPC authorization server (previously only available for HTTP authorization server).
Expand Down
51 changes: 40 additions & 11 deletions docs/root/intro/arch_overview/security/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,50 @@ Certificate selection
---------------------

:ref:`DownstreamTlsContexts <envoy_v3_api_msg_extensions.transport_sockets.tls.v3.DownstreamTlsContext>` support multiple TLS
certificates. These may be a mix of RSA and P-256 ECDSA certificates. The following rules apply:
certificates. These may be a mix of RSA and P-256 ECDSA certificates for multiple server name patterns.

* Only one certificate of a particular type (RSA or ECDSA) may be specified.
Certificate config/loading rules:

* DNS SANs or Subject Common Name is extracted as server name pattern to match SNI during handshake. Subject Common Name is not used if DNS SANs are present in the certificate.
* FQDN like "test.example.com" and wildcard like "\*.example.com" are valid at the same time, which will be loaded
as two different server name patterns.
* Only one certificate of a particular type (RSA or ECDSA) may be specified for each server name pattern.
LuyaoZhong marked this conversation as resolved.
Show resolved Hide resolved
* Non-P-256 server ECDSA certificates are rejected.
* If the client supports P-256 ECDSA, a P-256 ECDSA certificate will be selected if one is present in the
:ref:`DownstreamTlsContext <envoy_v3_api_msg_extensions.transport_sockets.tls.v3.DownstreamTlsContext>`
and it is in compliance with the OCSP policy.
* If the client only supports RSA certificates, a RSA certificate will be selected if present in the
:ref:`DownstreamTlsContext <envoy_v3_api_msg_extensions.transport_sockets.tls.v3.DownstreamTlsContext>`.
* Otherwise, the first certificate listed is used. This will result in a failed handshake if the
client only supports RSA certificates and the server only has ECDSA certificates.
* Static and SDS certificates may not be mixed in a given :ref:`DownstreamTlsContext
ggreenway marked this conversation as resolved.
Show resolved Hide resolved
<envoy_v3_api_msg_extensions.transport_sockets.tls.v3.DownstreamTlsContext>`.
* The selected certificate must adhere to the OCSP policy. If no
such certificate is found, the connection is refused.

Certificate selection rules:

* If the client supports SNI, e.g. SNI is "test.example.com", it looks for a cert that exactly matches to the SNI.
If the certificate adheres to the OCSP policy and matches to key type, it is selected for handshake.
If the certificate adheres to the OCSP policy, but key type is RSA while client is ECDSA capable, it is marked as
as the candidate and continues searching until a cert is selected with perfect match or certs exhausted.
Comment on lines +133 to +134
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 part gated by the full scan option? If so 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.

No, this part is not gated by full scan option.

Candidate will be selected for handshake if there is no perfect match.
* If the client supports SNI, but no cert is selected from certs that exactly matches to SNI, it matches on wildcard server name.
e.g. if SNI is "test.example.com", a certificate with "test.example.com" will be preferred over "\*.example.com". And wildcard
matching only works for 1 level of depth, so "\*.com" will not be a match for "test.example.com".
Afterwards, it execuates OCSP and key type checking on each cert which is the same as what happens after exact SNI matching.
* If no cert is selected from certs that matches wildcard name, the candidate cert is selected for handshake if it is present.
If there is no candidate, check :ref:`full_scan_certs_on_sni_mismatch <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.full_scan_certs_on_sni_mismatch>`,
go to full scan all certificates if it is enabled, otherwise pick the first certificate for handshake.
* If the client does not provide SNI at all, go to full scan no matter :ref:`full_scan_certs_on_sni_mismatch <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.full_scan_certs_on_sni_mismatch>`
is false or true.
* Full scan execuates OCSP and key type checking on each cert which is the same as described above in exact SNI matching.
It falls back to the first cert in the whole list if there is no cert selected.
* Currently only two kinds of key type are supported, RSA or ECDSA. If the client supports P-256 ECDSA, the P-256 ECDSA certificate
is preferred over RSA. The certificate that it falls back to might result in a failed handshake. For instance, a client only supports
RSA certificates and the certificate only support ECDSA.
* The final selected certificate must adhere to the OCSP policy. If no such certificate is found, the connection is refused.

.. note::
With the support of SNI-based certificate selection, it allows configuring large number of certificates for multiple hostnames.
:ref:`full_scan_certs_on_sni_mismatch <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.full_scan_certs_on_sni_mismatch>`
is introduced to determine if we continue full scan on SNI mismatch when the client provides SNI. SNI mismatch contains two cases in this context, one is there is no cert that matches to SNI,
another one is there are certs matches to SNI while OCSP policy fails on those certs. The :ref:`full_scan_certs_on_sni_mismatch <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.full_scan_certs_on_sni_mismatch>`
defaults to false, so full scan is disabled by default. The runtime flag ``envoy.reloadable_features.no_full_scan_certs_on_sni_mismatch``
can be used to override the default value of :ref:`full_scan_certs_on_sni_mismatch <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.full_scan_certs_on_sni_mismatch>`.
If full scan is enabled, it will look for the cert from the whole cert list on SNI mismatch, this could be a problem for a potential DoS attack because of O(n) complexity.


Only a single TLS certificate is supported today for :ref:`UpstreamTlsContexts
<envoy_v3_api_msg_extensions.transport_sockets.tls.v3.UpstreamTlsContext>`.
Expand Down
6 changes: 6 additions & 0 deletions envoy/ssl/context_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ class ServerContextConfig : public virtual ContextConfig {
* @return True if stateless TLS session resumption is disabled, false otherwise.
*/
virtual bool disableStatelessSessionResumption() const PURE;

/**
* @return True if we allow full scan certificates when there is no cert matching SNI during
* downstream TLS handshake, false otherwise.
*/
virtual bool fullScanCertsOnSNIMismatch() const PURE;
};

using ServerContextConfigPtr = std::unique_ptr<ServerContextConfig>;
Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ RUNTIME_GUARD(envoy_reloadable_features_local_ratelimit_match_all_descriptors);
RUNTIME_GUARD(envoy_reloadable_features_lua_respond_with_send_local_reply);
RUNTIME_GUARD(envoy_reloadable_features_no_delay_close_for_upgrades);
RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name);
RUNTIME_GUARD(envoy_reloadable_features_no_full_scan_certs_on_sni_mismatch);
RUNTIME_GUARD(envoy_reloadable_features_oauth_header_passthrough_fix);
RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout);
RUNTIME_GUARD(envoy_reloadable_features_postpone_h3_client_connect_to_next_loop);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,11 @@ ServerContextConfigImpl::ServerContextConfigImpl(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, require_client_certificate, false)),
ocsp_staple_policy_(ocspStaplePolicyFromProto(config.ocsp_staple_policy())),
session_ticket_keys_provider_(getTlsSessionTicketKeysConfigProvider(factory_context, config)),
disable_stateless_session_resumption_(getStatelessSessionResumptionDisabled(config)) {
disable_stateless_session_resumption_(getStatelessSessionResumptionDisabled(config)),
full_scan_certs_on_sni_mismatch_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(
config, full_scan_certs_on_sni_mismatch,
!Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.no_full_scan_certs_on_sni_mismatch"))) {
LuyaoZhong marked this conversation as resolved.
Show resolved Hide resolved

if (session_ticket_keys_provider_ != nullptr) {
// Validate tls session ticket keys early to reject bad sds updates.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ class ServerContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Ser
return disable_stateless_session_resumption_;
}

bool fullScanCertsOnSNIMismatch() const override { return full_scan_certs_on_sni_mismatch_; }

private:
static const unsigned DEFAULT_MIN_VERSION;
static const unsigned DEFAULT_MAX_VERSION;
Expand All @@ -191,6 +193,7 @@ class ServerContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Ser

absl::optional<std::chrono::seconds> session_timeout_;
const bool disable_stateless_session_resumption_;
bool full_scan_certs_on_sni_mismatch_;
};

} // namespace Tls
Expand Down
Loading