Skip to content
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
1 change: 1 addition & 0 deletions RAW_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ will make it substantially easier for the releaser to "linkify" all of the relea
final version.

## 1.6.0
* Added support for inline delivery of TLS certificates and private keys.
* Added gRPC healthcheck based on [grpc.health.v1.Health](https://github.com/grpc/grpc/blob/master/src/proto/grpc/health/v1/health.proto) service.
* Added Metrics Service implementation.
* Added gRPC access logging.
Expand Down
7 changes: 5 additions & 2 deletions configs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ envoy_py_test_binary(

genrule(
name = "example_configs",
srcs = ["//examples:configs"],
srcs = [
"//examples:configs",
"//test/config/integration/certs",
],
outs = ["example_configs.tar"],
cmd = "$(location configgen.sh) $(location configgen) $(@D) $(locations //examples:configs)",
cmd = "$(location configgen.sh) $(location configgen) $(@D) $(locations //examples:configs) $(locations //test/config/integration/certs)",
tools = [
"configgen.sh",
":configgen",
Expand Down
16 changes: 13 additions & 3 deletions configs/configgen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ shift
OUT_DIR="$1"
shift

mkdir -p "$OUT_DIR"
mkdir -p "$OUT_DIR/certs"
"$CONFIGGEN" "$OUT_DIR"
cp $* "$OUT_DIR"

for FILE in $*; do
case "$FILE" in
*.pem)
cp "$FILE" "$OUT_DIR/certs"
;;
*)
cp "$FILE" "$OUT_DIR"
;;
esac
done

# tar is having issues with -C for some reason so just cd into OUT_DIR.
(cd "$OUT_DIR"; tar -cvf example_configs.tar *.json *.yaml)
(cd "$OUT_DIR"; tar -cvf example_configs.tar *.json *.yaml certs/*.pem)
10 changes: 5 additions & 5 deletions configs/envoy_double_proxy.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"ssl_context": {
"alpn_protocols": "h2,http/1.1",
"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.

"private_key_file": "certs/serverkey.pem"
},
{% endif -%}
{% if proxy_proto -%}
Expand Down Expand Up @@ -128,8 +128,8 @@
up with a better solution just limit the requests
so we can cycle and get better spread. #}
"ssl_context": {
"cert_chain_file": "/etc/envoy/envoy-double-proxy.pem",
"private_key_file": "/etc/envoy/envoy-double-proxy.key",
"cert_chain_file": "certs/clientcert.pem",
"private_key_file": "certs/clientkey.pem",
"verify_subject_alt_name": ["front-proxy.yourcompany.net"]
},
"hosts": [{"url": "tcp://front-proxy.yourcompany.net:9400"}]
Expand All @@ -138,7 +138,7 @@
"name": "lightstep_saas",
"features": "http2",
"ssl_context": {
"ca_cert_file": "/etc/ssl/certs/ca-certificates.crt",
"ca_cert_file": "certs/cacert.pem",
"verify_subject_alt_name": ["collector-grpc.lightstep.com"]
},
"connect_timeout_ms": 1000,
Expand Down
8 changes: 4 additions & 4 deletions configs/envoy_front_proxy.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
"alpn_protocols": "h2,http/1.1",
"alt_alpn_protocols": "http/1.1",
{% if kwargs.get('pin_double_proxy_client', False) -%}
"ca_cert_file": "/etc/envoy/envoy-ca.pem",
"ca_cert_file": "certs/cacert.pem",
{# This should be the hash of the /etc/envoy/envoy-double-proxy.pem cert used in the
double proxy configuration. #}
"verify_certificate_hash": "fill me in",
{% endif -%}
"cert_chain_file": "/etc/envoy/cert.pem",
"private_key_file": "/etc/envoy/key.pem"
"cert_chain_file": "certs/servercert.pem",
"private_key_file": "certs/serverkey.pem"
},
{% endif -%}
{% if kwargs['proxy_proto'] -%}
Expand Down Expand Up @@ -144,7 +144,7 @@
"name": "lightstep_saas",
"features": "http2",
"ssl_context": {
"ca_cert_file": "/etc/ssl/certs/ca-certificates.crt",
"ca_cert_file": "certs/cacert.pem",
"verify_subject_alt_name": ["collector-grpc.lightstep.com"]
},
"connect_timeout_ms": 1000,
Expand Down
4 changes: 2 additions & 2 deletions configs/envoy_service_to_service.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@
"name": "egress_{{ host['name'] }}",
{% if host.get('ssl', False) -%}
"ssl_context": {
"ca_cert_file": "/etc/ssl/certs/ca-certificates.crt"
"ca_cert_file": "certs/cacert.pem"
{% if host.get('sni', False) -%}
,"sni": "{{ host['sni'] }}"
{% endif -%}
Expand Down Expand Up @@ -446,7 +446,7 @@
"name": "lightstep_saas",
"features": "http2",
"ssl_context": {
"ca_cert_file": "/etc/ssl/certs/ca-certificates.crt",
"ca_cert_file": "certs/cacert.pem",
"verify_subject_alt_name": ["collector-grpc.lightstep.com"]
},
"connect_timeout_ms": 1000,
Expand Down
30 changes: 24 additions & 6 deletions include/envoy/ssl/context_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,37 @@ class ContextConfig {
virtual const std::string& ecdhCurves() const PURE;

/**
* @return The CA certificate file to use for peer validation.
* @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.


/**
* @return The certificate chain file used to identify the local side.
* @return Path of the CA certificate to use for peer validation or "<inline>"
* if the CA certificate was inlined.
*/
virtual const std::string& certChainFile() const PURE;
virtual const std::string& caCertPath() const PURE;

/**
* @return The private key chain file used to identify the local side.
* @return The certificate chain used to identify the local side.
*/
virtual const std::string& privateKeyFile() const PURE;
virtual const std::string& certChain() const PURE;

/**
* @return Path of the certificate chain used to identify the local side or "<inline>"
* if the certificate chain was inlined.
*/
virtual const std::string& certChainPath() const PURE;

/**
* @return The private key used to identify the local side.
*/
virtual const std::string& privateKey() const PURE;

/**
* @return Path of the private key used to identify the local side or "<inline>"
* if the private key was inlined.
*/
virtual const std::string& privateKeyPath() const PURE;

/**
* @return The subject alt names to be verified, if enabled. Otherwise, ""
Expand Down
62 changes: 35 additions & 27 deletions source/common/ssl/context_config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,20 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::CommonTlsContext& con
RepeatedPtrUtil::join(config.tls_params().cipher_suites(), ":"), DEFAULT_CIPHER_SUITES)),
ecdh_curves_(StringUtil::nonEmptyStringOrDefault(
RepeatedPtrUtil::join(config.tls_params().ecdh_curves(), ":"), DEFAULT_ECDH_CURVES)),
ca_cert_file_(config.validation_context().trusted_ca().filename()),
cert_chain_file_(config.tls_certificates().empty()
ca_cert_(readDataSource(config.validation_context().trusted_ca(), true)),
ca_cert_path_(getDataSourcePath(config.validation_context().trusted_ca())),
cert_chain_(config.tls_certificates().empty()
? ""
: readDataSource(config.tls_certificates()[0].certificate_chain(), true)),
cert_chain_path_(config.tls_certificates().empty()
? ""
: config.tls_certificates()[0].certificate_chain().filename()),
private_key_file_(config.tls_certificates().empty()
: getDataSourcePath(config.tls_certificates()[0].certificate_chain())),
private_key_(config.tls_certificates().empty()
? ""
: readDataSource(config.tls_certificates()[0].private_key(), true)),
private_key_path_(config.tls_certificates().empty()
? ""
: config.tls_certificates()[0].private_key().filename()),
: getDataSourcePath(config.tls_certificates()[0].private_key())),
verify_subject_alt_name_list_(config.validation_context().verify_subject_alt_name().begin(),
config.validation_context().verify_subject_alt_name().end()),
verify_certificate_hash_(config.validation_context().verify_certificate_hash().empty()
Expand All @@ -61,6 +68,28 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::CommonTlsContext& con
ASSERT(config.validation_context().verify_certificate_hash().size() <= 1);
}

const std::string ContextConfigImpl::readDataSource(const envoy::api::v2::DataSource& source,
bool allow_empty) {
switch (source.specifier_case()) {
case envoy::api::v2::DataSource::kFilename:
return Filesystem::fileReadToEnd(source.filename());
case envoy::api::v2::DataSource::kInlineBytes:
return source.inline_bytes();
case envoy::api::v2::DataSource::kInlineString:
return source.inline_string();
default:
if (!allow_empty) {
throw EnvoyException(
fmt::format("Unexpected DataSource::specifier_case(): {}", source.specifier_case()));
}
return "";
}
}

const std::string ContextConfigImpl::getDataSourcePath(const envoy::api::v2::DataSource& source) {
return source.specifier_case() == envoy::api::v2::DataSource::kFilename ? source.filename() : "";
}

unsigned
ContextConfigImpl::tlsVersionFromProto(const envoy::api::v2::TlsParameters_TlsProtocol& version,
unsigned default_version) {
Expand Down Expand Up @@ -105,23 +134,7 @@ ServerContextConfigImpl::ServerContextConfigImpl(const envoy::api::v2::Downstrea
switch (config.session_ticket_keys_type_case()) {
case envoy::api::v2::DownstreamTlsContext::kSessionTicketKeys:
for (const auto& datasource : config.session_ticket_keys().keys()) {
switch (datasource.specifier_case()) {
case envoy::api::v2::DataSource::kFilename: {
validateAndAppendKey(ret, Filesystem::fileReadToEnd(datasource.filename()));
break;
}
case envoy::api::v2::DataSource::kInlineBytes: {
validateAndAppendKey(ret, datasource.inline_bytes());
break;
}
case envoy::api::v2::DataSource::kInlineString: {
validateAndAppendKey(ret, datasource.inline_string());
break;
}
default:
throw EnvoyException(fmt::format("Unexpected DataSource::specifier_case(): {}",
datasource.specifier_case()));
}
validateAndAppendKey(ret, readDataSource(datasource, false));
}
break;
case envoy::api::v2::DownstreamTlsContext::kSessionTicketKeysSdsSecretConfig:
Expand All @@ -139,12 +152,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.

// proper error handling.
// TODO(htuch): Support inline cert material delivery.
ASSERT(config.common_tls_context().tls_certificates().size() == 1);
ASSERT(config.common_tls_context().tls_certificates()[0].certificate_chain().specifier_case() ==
envoy::api::v2::DataSource::kFilename);
ASSERT(config.common_tls_context().tls_certificates()[0].private_key().specifier_case() ==
envoy::api::v2::DataSource::kFilename);
}

ServerContextConfigImpl::ServerContextConfigImpl(const Json::Object& config)
Expand Down
30 changes: 24 additions & 6 deletions source/common/ssl/context_config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,27 @@
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.


class ContextConfigImpl : public virtual Ssl::ContextConfig {
public:
// Ssl::ContextConfig
const std::string& alpnProtocols() const override { return alpn_protocols_; }
const std::string& altAlpnProtocols() const override { return alt_alpn_protocols_; }
const std::string& cipherSuites() const override { return cipher_suites_; }
const std::string& ecdhCurves() const override { return ecdh_curves_; }
const std::string& caCertFile() const override { return ca_cert_file_; }
const std::string& certChainFile() const override { return cert_chain_file_; }
const std::string& privateKeyFile() const override { return private_key_file_; }
const std::string& caCert() const override { return ca_cert_; }
const std::string& caCertPath() const override {
return (ca_cert_path_.empty() && !ca_cert_.empty()) ? INLINE_STRING : ca_cert_path_;
}
const std::string& certChain() const override { return cert_chain_; }
const std::string& certChainPath() const override {
return (cert_chain_path_.empty() && !cert_chain_.empty()) ? INLINE_STRING : cert_chain_path_;
}
const std::string& privateKey() const override { return private_key_; }
const std::string& privateKeyPath() const override {
return (private_key_path_.empty() && !private_key_.empty()) ? INLINE_STRING : private_key_path_;
}
const std::vector<std::string>& verifySubjectAltNameList() const override {
return verify_subject_alt_name_list_;
};
Expand All @@ -32,6 +43,10 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig {
protected:
ContextConfigImpl(const envoy::api::v2::CommonTlsContext& config);

static const std::string readDataSource(const envoy::api::v2::DataSource& source,
bool allow_empty);
static const std::string getDataSourcePath(const envoy::api::v2::DataSource& source);

private:
static unsigned tlsVersionFromProto(const envoy::api::v2::TlsParameters_TlsProtocol& version,
unsigned default_version);
Expand All @@ -43,9 +58,12 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig {
const std::string alt_alpn_protocols_;
const std::string cipher_suites_;
const std::string ecdh_curves_;
const std::string ca_cert_file_;
const std::string cert_chain_file_;
const std::string private_key_file_;
const std::string ca_cert_;
const std::string ca_cert_path_;
const std::string cert_chain_;
const std::string cert_chain_path_;
const std::string private_key_;
const std::string private_key_path_;
const std::vector<std::string> verify_subject_alt_name_list_;
const std::string verify_certificate_hash_;
const unsigned min_protocol_version_;
Expand Down
Loading