Skip to content

tls: inline delivery of TLS certificates and private keys.#2248

Merged
mattklein123 merged 11 commits intoenvoyproxy:masterfrom
PiotrSikora:ssl_inline
Jan 22, 2018
Merged

tls: inline delivery of TLS certificates and private keys.#2248
mattklein123 merged 11 commits intoenvoyproxy:masterfrom
PiotrSikora:ssl_inline

Conversation

@PiotrSikora
Copy link
Copy Markdown
Contributor

File-based BoringSSL APIs are no longer used and everything is processed
as if it was delivered inline in order to provide consistent behavior.

Additionally, DataSource files are now read during configuration parsing,
which means that the configuration is considered invalid if it refers to
non-existing files.

Fixes #1357.

Signed-off-by: Piotr Sikora piotrsikora@google.com

File-based BoringSSL APIs are no longer used and everything is processed
as if it was delivered inline in order to provide consistent behavior.

Additionally, DataSource files are now read during configuration parsing,
which means that the configuration is considered invalid if it refers to
non-existing files.

Fixes envoyproxy#1357.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
@PiotrSikora
Copy link
Copy Markdown
Contributor Author

@mattklein123 @ggreenway @jvshahid Please take a look.

"alt_alpn_protocols": "http/1.1",
"cert_chain_file": "/etc/envoy/cert.pem",
"private_key_file": "/etc/envoy/key.pem"
"cert_chain_file": "certs/servercert.pem",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Quick comment which unfortunately is going to make this more complicated: We are going to need to fix config_test to work with some type of mock loader that doesn't read files from disk. Optimally it would verify everything else in SSL and possibly just use fake valid cert data. The reason this is going to be needed is we run at Lyft config tests for configs where the certs are not available on the box where the tests are run.

Ultimately I think we know this is working when none of the example config stuff here changed as part of this diff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have mixed feelings about validity of this approach, but I'll add this if you need it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm sorry, but we definitely need it. I would just figure out whatever is easiest in that test, even if you have to mock out more stuff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I think I might have misunderstood what you were saying.

I assumed that you meant letting //test/config_test:example_configs_test pass without reading certificates from disk, but after some digging, I've noticed that //test/tools/config_load_check:config_load_check_tool uses the same code, so you probably meant that you're using this tool to verify production configs and want to test them without access to the real certificates? Is that correct?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We actually use both a private version of the config test as well as the config load check tool (in different places). We need both to run in a mode that doesn't require the actual cert files to exist (because yes we test prod configs where the certs don't live). IMO it's fine to just have them both work the same way for now (don't load) which is not a regression from what happened before, so any kind of mock is fine. The optimal solution would be to not use a full mock TLS context loader so we can check other config options, but to only mock the actual cert loading part. I have no idea if that is easy or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can open an issue to do what you describe, but if we want to merge this sooner we need to not break existing behavior. IMO having a flag on whether to check the cert or not makes a lot of sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree with @ggreenway, it's one thing to mock things in tests, but faking certificates when testing production config sounds like a really bad idea, and if you're testing those on a machine without access to real certificates, then you should provide self-signed certs. Otherwise, you might refer to certificates that don't exist (e.g. typo).

Furthermore, we'd need to add same hacks to --mode validate, which sounds even worse.

Yes, it's change of current behavior, but I don't see how this justifies adding error-prone hacks to the code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I hear what you are saying, but, that's the way it works today, and this PR will break us. If we want to hold this PR until we fix the way we test things, we can do that, but it will take 1-2 weeks since we will need to schedule the work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'd prefer to wait, then.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Alright I will make an internal ticket and get someone to work on cutting us over to fake certs for testing next week.

@@ -135,12 +153,7 @@ ServerContextConfigImpl::ServerContextConfigImpl(const envoy::api::v2::Downstrea
// TODO(PiotrSikora): Support multiple TLS certificates.
// TODO(mattklein123): All of the ASSERTs in this file need to be converted to exceptions with
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@PiotrSikora as long as you are in here, can you take a look at the rest of the asserts in this file? I think some of them could potentially happen/crash with native v2 config. I might be wrong, but could you take a look and potentially fix to throw/add tests? (Follow up is fine if that is better).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'll do in a separate PR.

uint32_t num_tested = 0;
for (const std::string& filename : TestUtility::listFiles(directory, true)) {
// Change working directory, otherwise we won't be able to read files using relative paths.
RELEASE_ASSERT(::chdir(directory.c_str()) == 0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Per above we need this to still work with cert files that can't be read. Sorry. :(

Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

Rad, thanks!

: ""),
cert_chain_(config.tls_certificates().empty()
? ""
: readDataSource(config.tls_certificates()[0].certificate_chain(), true)),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a reasonable way to make the initializors here a bit cleaner, e.g. with anonymous namespace functions etc.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FWIW I prefer to have it all inline; having to track down a bunch of functions to understand how everything is initialized makes it harder to read, IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

While I agree that this isn't the prettiest code, I'm with Greg on this, it's much more readable here.

for (;;) {
bssl::UniquePtr<X509> cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
if (cert == nullptr) {
break;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we have full test coverage for all these branches and exception cases?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unlikely, I'll add some.

bssl::UniquePtr<BIO> bio(
BIO_new_mem_buf(const_cast<char*>(config.caCert().data()), config.caCert().size()));
RELEASE_ASSERT(bio != nullptr);
// Based on BoringSSL's X509_load_cert_crl_file().
Copy link
Copy Markdown
Member

@htuch htuch Dec 21, 2017

Choose a reason for hiding this comment

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

Not familiar enough with BoringSSL to know if this is the best we can do. Is there no way to do a non-filesystem version of things like SSL_CTX_load_verify_locations other than reimplementing bits of BoringSSL? Seems like the kind of place tmpfs could be useful but this isn't portable or even easy to manage at user privilege levels. @davidben for thoughts as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's unfortunate that the openssl/boringssl has nice APIs for files, but not an analogous API for a BIO. But since that seems to be the way it is for now, I think this is the best we can do. I think a tmpfs is a huge can of worms to open up, with security and operational issues.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, there are no non-filesystem equivalents of the APIs we use, and even if I could add them today, we're on the chromium-stable branch, which is ~2 months behind the master branch, so it wouldn't help anyway.

I have a mid-term plan to migrate away from X509 APIs to BoringSSL-specific CRYPTO_BUFFERs and associated APIs, but that's further down the line, and it requires custom certificate verification logic.

* @return The CA certificate to use for peer validation.
*/
virtual const std::string& caCertFile() const PURE;
virtual const std::string& caCert() const PURE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make more sense to return std::vector<uint8_t> or something? Although protobuf uses std::string for non-string buffers, so we might be stuck with this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe? I'm fine with either, but as you noted, protobuf uses std::string for the bytes inline field, so we might as well stick to it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed; let's stick with what you had.

int rc = SSL_CTX_load_verify_locations(ctx_.get(), config.caCertFile().c_str(), nullptr);
if (0 == rc) {
if (!config.caCert().empty()) {
ca_file_path_ = config.caCertPath();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The only place this value is used is in ContextImpl::getCaCertInformation() (just to print the path of the cert, which is output by an admin handler). Can we just get rid of it entirely? It made sense when all config pointed to a path on disk; it makes less sense now and adds complication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's also used in the error messages.

bssl::UniquePtr<BIO> bio(
BIO_new_mem_buf(const_cast<char*>(config.caCert().data()), config.caCert().size()));
RELEASE_ASSERT(bio != nullptr);
// Based on BoringSSL's X509_load_cert_crl_file().
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's unfortunate that the openssl/boringssl has nice APIs for files, but not an analogous API for a BIO. But since that seems to be the way it is for now, I think this is the best we can do. I think a tmpfs is a huge can of worms to open up, with security and operational issues.

…ine.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

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

Overall this looks good. A few small comments.

And we need to wait until @mattklein123 or someone from lyft says that they're ready for the config-test change in behavior.


/**
* @return The certificate chain file used to identify the local side.
* @return Path of the CA certificate to use for peer validation.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For each of the *Path() functions, please document that it will return an empty string in the case of inline material.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

? ""
: config.tls_certificates()[0].private_key().filename()),
ca_cert_(readDataSource(config.validation_context().trusted_ca(), true)),
ca_cert_path_(config.validation_context().trusted_ca().specifier_case() ==
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a helper for getting the path in the case it is kFilename, since this code is repeated several times.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

namespace Envoy {
namespace Ssl {

static const std::string INLINE_STRING = "<inline>";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

From an interface point of view, I'd rather have all these functions return empty string if they're inline, and then change that to this human-readable string right before it is presented to a user. But I don't feel strongly about it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

From an interface point of view, I think it's better to have this logic self-contained here, instead of intermixed with the code. I don't feel too strongly about it, but I'm going to leave it as-is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's fine; if someone ever needs it different, it's easy enough to change then.

fmt::format("Failed to load certificate chain from {}", config.certChainPath()));
}
// Read rest of the certificate chain.
for (;;) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"while (true)" is the convention used in this codebase

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

@mattklein123
Copy link
Copy Markdown
Member

@rodaine is going to fix our internal tests. He can comment when it is done. Hopefully this week.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>

/**
* @return The certificate chain file used to identify the local side.
* @return Path of the CA certificate to use for peer validation or an empty string
Copy link
Copy Markdown
Member

@ggreenway ggreenway Jan 17, 2018

Choose a reason for hiding this comment

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

This returns "<inline>" not "" for inline data.

Sorry, I made my original comment before I'd gotten to the implementation of the interface.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sigh, I should have known better! Thanks.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
ggreenway
ggreenway previously approved these changes Jan 17, 2018
Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

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

LGTM. I think this can be merged after @rodaine indicates that Lyft is ready for the change.

@ggreenway
Copy link
Copy Markdown
Member

@PiotrSikora You also have a merge conflict in the release notes that needs resolution.

mattklein123
mattklein123 previously approved these changes Jan 17, 2018
Copy link
Copy Markdown
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.

Thanks, looks sweet once we fix our internal tooling.

@PiotrSikora
Copy link
Copy Markdown
Contributor Author

@ggreenway yeah, I know... but even if I resolve it now, I expect that there will be another conflict on the release notes before we get a green light.

@rodaine
Copy link
Copy Markdown
Member

rodaine commented Jan 17, 2018

Working on this today. Will keep you posted!

…ine.

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
@PiotrSikora PiotrSikora dismissed stale reviews from mattklein123 and ggreenway via 303a4c1 January 19, 2018 22:00
RELEASE_ASSERT(::chdir(directory.c_str()) == 0);
uint32_t num_tested = 0;
for (const std::string& filename : TestUtility::listFiles(directory, true)) {
for (const std::string& filename : TestUtility::listFiles(directory, false)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Out of curiosity, is there a reason this recursive flag was flipped to false?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unit test certificates are in the certs sub-directory, and we don't want to load configuration from them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! Missed that.

Copy link
Copy Markdown
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.

I think we are good to go with this. Thank you!

@mattklein123 mattklein123 merged commit 01ee725 into envoyproxy:master Jan 22, 2018
jpsim pushed a commit that referenced this pull request Nov 28, 2022
Bintray has been deprecated and should be removed as a source.

Signed-off-by: ben@ben.cm
Signed-off-by: JP Simard <jp@jpsim.com>
jpsim pushed a commit that referenced this pull request Nov 29, 2022
Bintray has been deprecated and should be removed as a source.

Signed-off-by: ben@ben.cm
Signed-off-by: JP Simard <jp@jpsim.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.

5 participants