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

tls: enable multiple SSL certificate support. #5317

Merged
merged 16 commits into from
Dec 17, 2018
Merged

Conversation

htuch
Copy link
Member

@htuch htuch commented Dec 16, 2018

This PR wraps up #1319. The patch enables multiple TLS certificate
ingest for downstream TLS contexts, adds related unit and integration
tests, docs and release notes.

Risk Level: Low
Testing: Additional unit and integration tests. To avoid combinatorial
explosion, we validate mixed TLS v1.2/1.3 behavior in
ssl_integration_test only, and have more targeted certificate
selection tests in ssl_socket_Test.
Docs Changes: Added to architectural overview of TLS support.

Fixes #1319.

Signed-off-by: Harvey Tuch htuch@google.com

This PR wraps up envoyproxy#1319. The patch enables multiple TLS certificate
ingest for upstream TLS contexts, adds related unit and integration
tests, docs and release notes.

Risk Level: Low
Testing: Additional unit and integration tests. To avoid combinatorial
  explosion, we validate mixed TLS v1.2/1.3 behavior in
  ssl_integration_test only, and have more targeted certificate
  selection tests in ssl_socket_Test.
Docs Changes: Added to architectural overview of TLS support.

Fixes envoyproxy#1319.

Signed-off-by: Harvey Tuch <htuch@google.com>
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Quick drive by. Very cool to see this implemented!

// Multiple TLS certificates can be associated with the same context.
// E.g. to allow both RSA and ECDSA certificates, two TLS certificates can be configured.
// :ref:`Multiple TLS certificates <arch_overview_ssl_multi_cert>` can be associated with the same
// context.B E.g. to allow both RSA and ECDSA certificates, two TLS certificates can be
Copy link
Member

Choose a reason for hiding this comment

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

typo ".B"

Multiple certificates
---------------------

:ref:`UpstreamTlsContexts <envoy_api_msg_auth.UpstreamTlsContext>` support multiple TLS
Copy link
Member

Choose a reason for hiding this comment

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

I think it's the reverse? We support multiple contexts for downstream but not for upstream?

---------------------

:ref:`UpstreamTlsContexts <envoy_api_msg_auth.UpstreamTlsContext>` support multiple TLS
certificates. These may be a mix of RSA and P-256 ECDSA certificates. The following rules apply:
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that I asked about this before, but could we enforce that at most single RSA and single P-256 ECDSA are configured? We know that anything more is never going to be used at runtime, so it's most likely misconfiguration. This should only require a few easy checks in ContextImpl() and ServerContextConfigImpl().

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, OK, let's go with this, it simplifies other logic later on.

certificates. These may be a mix of RSA and P-256 ECDSA certificates. The following rules apply:

* Only the first certificate of a particular type (RSA or ECDSA) is considered.
* Non-P256 server ECDSA certificates are rejected.
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't specific to multiple certificates.

(We should have it documented somewhere, but probably not here.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I will rename this section to something like "Certificate selection" and we can keep it here.


* Only the first certificate of a particular type (RSA or ECDSA) is considered.
* Non-P256 server ECDSA certificates are rejected.
* The client must indicate P-256 support to be considered ECDSA capable.
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is redundant with the more descriptive line below.

// Multiple TLS certificates can be associated with the same context.
// E.g. to allow both RSA and ECDSA certificates, two TLS certificates can be configured.
// :ref:`Multiple TLS certificates <arch_overview_ssl_multi_cert>` can be associated with the same
// context.B E.g. to allow both RSA and ECDSA certificates, two TLS certificates can be
Copy link
Contributor

Choose a reason for hiding this comment

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

s/B E.g./, e.g./, but I'd probably rephrase the whole thing as:

// :ref:`Multiple TLS certificates <arch_overview_ssl_multi_cert> can be associated with the same
// context to allow both RSA and ECDSA certificates.

@@ -78,6 +78,7 @@ Version history
* tls: added support for CRLs in :ref:`trusted_ca <envoy_api_field_auth.CertificateValidationContext.trusted_ca>`.
* tls: added support for :ref:`password encrypted private keys <envoy_api_field_auth.TlsCertificate.password>`.
* tls: added ssl.versions.<version> to :ref:`listener metrics <config_listener_stats>` to track TLS versions in use.
* tls: added support for :ref:`multiple server TLS certificates <arch_overview_ssl_multi_cert>`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: move it above ssl.versions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think ssl.versions should be moved above the other to enforce alpha order.

if ((config.common_tls_context().tls_certificates().size() +
config.common_tls_context().tls_certificate_sds_secret_configs().size()) == 0) {
throw EnvoyException("No TLS certificates found for server context");
} else if ((config.common_tls_context().tls_certificates().size() +
config.common_tls_context().tls_certificate_sds_secret_configs().size()) > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I recall, we still only support single certificate served over SDS, don't we?

Copy link
Member

Choose a reason for hiding this comment

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

I think you may have multiple sds config for single cluster/listener? https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/auth/cert.proto#L239

Copy link
Contributor

Choose a reason for hiding this comment

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

But it doesn't work with multiple certificates, because callback clears all other certificates on update, see:

// This breaks multiple certificate support, but today SDS is only single cert.

ASSERT_EQ(3, tls_certs.size());
EXPECT_THAT(tls_certs[0].get().privateKeyPath(), EndsWith("selfsigned_key.pem"));
EXPECT_THAT(tls_certs[1].get().privateKeyPath(), EndsWith("selfsigned_key_ecdsa_p256.pem"));
EXPECT_THAT(tls_certs[2].get().privateKeyPath(), EndsWith("selfsigned_key.pem"));
Copy link
Contributor

Choose a reason for hiding this comment

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

Erm, we really shouldn't be able to load the same certificate twice.

Multiple certificates
---------------------

:ref:`UpstreamTlsContexts <envoy_api_msg_auth.UpstreamTlsContext>` support multiple TLS
Copy link
Contributor

Choose a reason for hiding this comment

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

Downstream contexts support multiple TLS certificates, not upstream.

* The client must indicate P-256 support to be considered ECDSA capable.
* If the client supports P-256 ECDSA, a P-256 ECDSA certificate will be selected if present in the
:ref:`UpstreamTlsContext <envoy_api_msg_auth.UpstreamTlsContext>`.
* If the client only supports RSA certificate, an RSA certificate will be selected if present in the
Copy link
Contributor

Choose a reason for hiding this comment

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

s/certificate/certificates/, s/an/a/(?)

@repokitteh-read-only
Copy link

🙀 Error while processing event:

invalid command
error: invalid command line string
🐱

Caused by: a #5317 (comment) was created by @PiotrSikora.

see: more, trace.

@lizan lizan self-assigned this Dec 16, 2018
Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
throw EnvoyException("A single TLS certificate is required for server contexts");
} else if (!config.common_tls_context().tls_certificates().empty() &&
!config.common_tls_context().tls_certificate_sds_secret_configs().empty()) {
throw EnvoyException("Static and dynamic TLS certificates may not be mixed in server contexts");
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this isn't really "static" and "dynamic", since LDS/CDS and SDS can be both "static" and "dynamic".

Maybe just say that (i.e. LDS/CDS and SDS TLS certificates may not be mixed in server contexts) or is it too nitty-gritty detail for operators?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think "Non-SDS and SDS TLS certificates may not be mixed.." is clearer here, as LDS/CDS doesn't refer to bootstrap static config.

Copy link
Contributor

Choose a reason for hiding this comment

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

secrets (i.e. "static" SDS) can be also in the bootstrap static config.

But yeah, I agree that it sounds cleaner.

@@ -259,6 +267,12 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const ContextConfig& config, TimeS
"ECDSA certificates are supported",
ctx.cert_chain_file_path_));
}
} else if (have_rsa_cert) {
Copy link
Contributor

Choose a reason for hiding this comment

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

There are other certificate types (though, they are not very popular), but this code path assumes that !ECDSA == RSA, so it would potentially reject those.

)EOF";

EXPECT_THROW_WITH_REGEX(testUtil(client_ctx_yaml, server_ctx_yaml, "", "", "", "", "", "", "",
"ssl.no_certificate", true, GetParam()),
Copy link
Contributor

Choose a reason for hiding this comment

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

The stat and connected status seem bogus here, since this will throw before reaching that stage.

Maybe consider moving those "bad configuration" tests to test/common/ssl/context_impl_test.cc, where we usually test them?

)EOF";

EXPECT_THROW_WITH_REGEX(testUtil(client_ctx_yaml, server_ctx_yaml, "", "", "", "", "", "", "",
"ssl.no_certificate", true, GetParam()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_ecdsa_p256_key.pem"
)EOF";

testUtil(client_ctx_yaml, server_ctx_yaml, "", "", "", "", "", "", "", "ssl.no_certificate", true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you test a more "happy" stat, e.g. ssl.handshake?

echo "constexpr char TEST_$(echo $1 | tr a-z A-Z)_CERT_SERIAL[] = \"$(openssl x509 -in $1_cert.pem -noout -serial | cut -d"=" -f2 | awk '{print tolower($0)}')\";" >> $1_cert_info.h
echo "constexpr char TEST_$(echo $1 | tr a-z A-Z)_CERT_NOT_BEFORE[] = \"$(openssl x509 -in $1_cert.pem -noout -startdate | cut -d"=" -f2)\";" >> $1_cert_info.h
echo "constexpr char TEST_$(echo $1 | tr a-z A-Z)_CERT_NOT_AFTER[] = \"$(openssl x509 -in $1_cert.pem -noout -enddate | cut -d"=" -f2)\";" >> $1_cert_info.h
generate_info_header $1
}

# $1=<certificate name>
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: add $2=[certificate file name](?).

Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Copy link
Contributor

@PiotrSikora PiotrSikora left a comment

Choose a reason for hiding this comment

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

LGTM, other than the 2 typos and too many test certs, but feel free to ignore it if you disagree with my comment.

@@ -41,6 +41,11 @@ rm -f server_ecdsacert.cfg
# Generate cert for the client.
generate_rsa_key client ca
generate_x509_cert client ca
# Generate ECDSA cert for the celitn.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/celint/client/

ctx.is_ecdsa_ = EVP_PKEY_id(public_key.get()) == EVP_PKEY_EC;
const int pkey_id = EVP_PKEY_id(public_key.get());
if (!cert_pkey_ids.insert(pkey_id).second) {
throw EnvoyException(fmt::format("Failed to load certificate from chain {}, at most one "
Copy link
Contributor

Choose a reason for hiding this comment

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

s/certificate from chain/certificate chain from/

private_key:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem"
- certificate_chain:
filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned2_cert.pem"
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you re-use existing certs (selfsigned & selfsigned_rsa_1024* and selfsigned_ecdsa_p256 and selfsigned_ecdsa_p384) instead of adding another set? We have a bit too many test certs already, and we could get rid of the OUTPUT_PREFIX hack in certs.sh, then.

*either copy it from #5318 or wait until it's merged (later today?)

Copy link
Member Author

Choose a reason for hiding this comment

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

I think if we did that, it might work, because we check for duplicate certs of a given type before validating the cert, but this is just an implementation detail which could potentially change so let's stick with this for now.

Long term, I think the right direction to go here is to have some custom Go/Python script for programatically generating the certs (since we can't rely on the openssl CLI in any Bazel automation), so we don't need to check in so many certs.

Copy link
Member

Choose a reason for hiding this comment

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

Long term, I think the right direction to go here is to have some custom Go/Python script for programatically generating the certs (since we can't rely on the openssl CLI in any Bazel automation), so we don't need to check in so many certs.

We used to do openssl in genrule and that broke once in OSX, but I think we may enable that again by using bssl binary?

Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Harvey Tuch <htuch@google.com>
Copy link
Contributor

@PiotrSikora PiotrSikora left a comment

Choose a reason for hiding this comment

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

Thanks!

@htuch htuch merged commit fdb0880 into envoyproxy:master Dec 17, 2018
@htuch htuch deleted the multi-cert branch December 17, 2018 22:39
fredlas pushed a commit to fredlas/envoy that referenced this pull request Mar 5, 2019
This PR wraps up envoyproxy#1319. The patch enables multiple TLS certificate
ingest for downstream TLS contexts, adds related unit and integration
tests, docs and release notes.

Risk Level: Low
Testing: Additional unit and integration tests. To avoid combinatorial
explosion, we validate mixed TLS v1.2/1.3 behavior in
ssl_integration_test only, and have more targeted certificate
selection tests in ssl_socket_Test.
Docs Changes: Added to architectural overview of TLS support.

Fixes envoyproxy#1319.

Signed-off-by: Harvey Tuch <htuch@google.com>
Signed-off-by: Fred Douglas <fredlas@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants