Nack certain unsupported fields in CertificateValidationContext (#26880)

* Nack certain unsupported fields in CertificateValidationContext

* Regenerate projects

* Reviewer comments
pull/27070/head
Yash Tibrewal 3 years ago committed by GitHub
parent e386d4036b
commit 0ab13090e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      CMakeLists.txt
  2. 1
      build_autogenerated.yaml
  3. 56
      src/core/ext/xds/xds_api.cc
  4. 10
      src/proto/grpc/testing/xds/v3/BUILD
  5. 3
      src/proto/grpc/testing/xds/v3/base.proto
  6. 38
      src/proto/grpc/testing/xds/v3/extension.proto
  7. 87
      src/proto/grpc/testing/xds/v3/tls.proto
  8. 144
      test/cpp/end2end/xds_end2end_test.cc

7
CMakeLists.txt generated

@ -487,6 +487,9 @@ protobuf_generate_grpc_cpp(
protobuf_generate_grpc_cpp(
src/proto/grpc/testing/xds/v3/endpoint.proto
)
protobuf_generate_grpc_cpp(
src/proto/grpc/testing/xds/v3/extension.proto
)
protobuf_generate_grpc_cpp(
src/proto/grpc/testing/xds/v3/fault.proto
)
@ -15767,6 +15770,10 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.pb.h

@ -7079,6 +7079,7 @@ targets:
- src/proto/grpc/testing/xds/v3/csds.proto
- src/proto/grpc/testing/xds/v3/discovery.proto
- src/proto/grpc/testing/xds/v3/endpoint.proto
- src/proto/grpc/testing/xds/v3/extension.proto
- src/proto/grpc/testing/xds/v3/fault.proto
- src/proto/grpc/testing/xds/v3/fault_common.proto
- src/proto/grpc/testing/xds/v3/http_connection_manager.proto

@ -1976,6 +1976,7 @@ grpc_error_handle CommonTlsContextParse(
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*
common_tls_context_proto,
XdsApi::CommonTlsContext* common_tls_context) {
std::vector<grpc_error_handle> errors;
auto* combined_validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_combined_validation_context(
common_tls_context_proto);
@ -2023,8 +2024,9 @@ grpc_error_handle CommonTlsContextParse(
matcher = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid StringMatcher specified");
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid StringMatcher specified"));
continue;
}
bool ignore_case = envoy_type_matcher_v3_StringMatcher_ignore_case(
subject_alt_names_matchers[i]);
@ -2032,19 +2034,54 @@ grpc_error_handle CommonTlsContextParse(
StringMatcher::Create(type, matcher,
/*case_sensitive=*/!ignore_case);
if (!string_matcher.ok()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("string matcher: ",
string_matcher.status().message())
.c_str());
.c_str()));
continue;
}
if (type == StringMatcher::Type::kSafeRegex && ignore_case) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"StringMatcher: ignore_case has no effect for SAFE_REGEX.");
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"StringMatcher: ignore_case has no effect for SAFE_REGEX."));
continue;
}
common_tls_context->combined_validation_context
.default_validation_context.match_subject_alt_names.push_back(
std::move(string_matcher.value()));
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_spki(
default_validation_context, nullptr) != nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"CertificateValidationContext: verify_certificate_spki "
"unsupported"));
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_hash(
default_validation_context, nullptr) != nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"CertificateValidationContext: verify_certificate_hash "
"unsupported"));
}
auto* require_signed_certificate_timestamp =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp(
default_validation_context);
if (require_signed_certificate_timestamp != nullptr &&
google_protobuf_BoolValue_value(
require_signed_certificate_timestamp)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"CertificateValidationContext: "
"require_signed_certificate_timestamp unsupported"));
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_crl(
default_validation_context)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"CertificateValidationContext: crl unsupported"));
}
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_custom_validator_config(
default_validation_context)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"CertificateValidationContext: custom_validator_config "
"unsupported"));
}
}
auto* validation_context_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_validation_context_certificate_provider_instance(
@ -2054,7 +2091,7 @@ grpc_error_handle CommonTlsContextParse(
context, validation_context_certificate_provider_instance,
&common_tls_context->combined_validation_context
.validation_context_certificate_provider_instance);
if (error != GRPC_ERROR_NONE) return error;
if (error != GRPC_ERROR_NONE) errors.push_back(error);
}
}
auto* tls_certificate_certificate_provider_instance =
@ -2064,9 +2101,10 @@ grpc_error_handle CommonTlsContextParse(
grpc_error_handle error = CertificateProviderInstanceParse(
context, tls_certificate_certificate_provider_instance,
&common_tls_context->tls_certificate_certificate_provider_instance);
if (error != GRPC_ERROR_NONE) return error;
if (error != GRPC_ERROR_NONE) errors.push_back(error);
}
return GRPC_ERROR_NONE;
return GRPC_ERROR_CREATE_FROM_VECTOR("Error parsing CommonTlsContext",
&errors);
}
grpc_error_handle HttpConnectionManagerParse(

@ -104,6 +104,14 @@ grpc_proto_library(
],
)
grpc_proto_library(
name = "extension_proto",
srcs = [
"extension.proto",
],
well_known_protos = True,
)
grpc_proto_library(
name = "listener_proto",
srcs = [
@ -244,6 +252,8 @@ grpc_proto_library(
],
well_known_protos = True,
deps = [
"base_proto",
"extension_proto",
"string_proto",
],
)

@ -96,6 +96,9 @@ message Node {
repeated string client_features = 10;
}
// Data source consisting of either a file or an inline value.
message DataSource {}
// Runtime derived FractionalPercent with defaults for when the numerator or denominator is not
// specified via a runtime key.
//

@ -0,0 +1,38 @@
// Copyright 2021 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Local copy of Envoy xDS proto file, used for testing only.
syntax = "proto3";
package envoy.config.core.v3;
import "google/protobuf/any.proto";
// [#protodoc-title: Extension configuration]
// Message type for extension configuration.
// [#next-major-version: revisit all existing typed_config that doesn't use this wrapper.].
message TypedExtensionConfig {
// The name of an extension. This is not used to select the extension, instead
// it serves the role of an opaque identifier.
string name = 1;
// The typed config for the extension. The type URL will be used to identify
// the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*,
// the inner type URL of *TypedStruct* will be utilized. See the
// :ref:`extension configuration overview
// <config_overview_extension_configuration>` for further details.
google.protobuf.Any typed_config = 2;
}

@ -18,16 +18,78 @@ syntax = "proto3";
package envoy.extensions.transport_sockets.tls.v3;
import "src/proto/grpc/testing/xds/v3/base.proto";
import "src/proto/grpc/testing/xds/v3/extension.proto";
import "src/proto/grpc/testing/xds/v3/string.proto";
import "google/protobuf/wrappers.proto";
message CertificateValidationContext {
// An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the
// SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate
// matches one of the specified values.
//
// A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate
// can be generated with the following command:
//
// .. code-block:: bash
//
// $ openssl x509 -in path/to/client.crt -noout -pubkey
// | openssl pkey -pubin -outform DER
// | openssl dgst -sha256 -binary
// | openssl enc -base64
// NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A=
//
// This is the format used in HTTP Public Key Pinning.
//
// When both:
// :ref:`verify_certificate_hash
// <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.verify_certificate_hash>` and
// :ref:`verify_certificate_spki
// <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.verify_certificate_spki>` are specified,
// a hash matching value from either of the lists will result in the certificate being accepted.
//
// .. attention::
//
// This option is preferred over :ref:`verify_certificate_hash
// <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.verify_certificate_hash>`,
// because SPKI is tied to a private key, so it doesn't change when the certificate
// is renewed using the same private key.
repeated string verify_certificate_spki = 3;
// An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that
// the SHA-256 of the DER-encoded presented certificate matches one of the specified values.
//
// A hex-encoded SHA-256 of the certificate can be generated with the following command:
//
// .. code-block:: bash
//
// $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2
// df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a
//
// A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate
// can be generated with the following command:
//
// .. code-block:: bash
//
// $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2
// DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A
//
// Both of those formats are acceptable.
//
// When both:
// :ref:`verify_certificate_hash
// <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.verify_certificate_hash>` and
// :ref:`verify_certificate_spki
// <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.verify_certificate_spki>` are specified,
// a hash matching value from either of the lists will result in the certificate being accepted.
repeated string verify_certificate_hash = 2;
// An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the
// Subject Alternative Name of the presented certificate matches one of the specified matchers.
//
// When a certificate has wildcard DNS SAN entries, to match a specific client, it should be
// configured with exact match type in the :ref:`string matcher <envoy_api_msg_type.matcher.v3.StringMatcher>`.
// configured with exact match type in the :ref:`string matcher <envoy_v3_api_msg_type.matcher.v3.StringMatcher>`.
// For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com",
// it should be configured as shown below.
//
@ -40,8 +102,29 @@ message CertificateValidationContext {
//
// Subject Alternative Names are easily spoofable and verifying only them is insecure,
// therefore this option must be used together with :ref:`trusted_ca
// <envoy_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.trusted_ca>`.
// <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.trusted_ca>`.
repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9;
// [#not-implemented-hide:] Must present signed certificate time-stamp.
google.protobuf.BoolValue require_signed_certificate_timestamp = 6;
// An optional `certificate revocation list
// <https://en.wikipedia.org/wiki/Certificate_revocation_list>`_
// (in PEM format). If specified, Envoy will verify that the presented peer
// certificate has not been revoked by this CRL. If this DataSource contains
// multiple CRLs, all of them will be used. Note that if a CRL is provided
// for any certificate authority in a trust chain, a CRL must be provided
// for all certificate authorities in that chain. Failure to do so will
// result in verification failure for both revoked and unrevoked certificates
// from that chain.
config.core.v3.DataSource crl = 7;
// The configuration of an extension specific certificate validator.
// If specified, all validation is done by the specified validator,
// and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated).
// Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field.
// [#extension-category: envoy.tls.cert_validator]
config.core.v3.TypedExtensionConfig custom_validator_config = 12;
}
message UpstreamTlsContext {

@ -8413,6 +8413,150 @@ TEST_P(XdsSecurityTest, UnknownIdentityCertificateProvider) {
g_fake1_cert_data_map = nullptr;
}
TEST_P(XdsSecurityTest,
NacksCertificateValidationContextWithVerifyCertificateSpki) {
FakeCertificateProvider::CertDataMap fake1_cert_map = {
{"", {root_cert_, identity_pair_}}};
g_fake1_cert_data_map = &fake1_cert_map;
auto cluster = default_cluster_;
auto* transport_socket = cluster.mutable_transport_socket();
transport_socket->set_name("envoy.transport_sockets.tls");
UpstreamTlsContext upstream_tls_context;
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_validation_context_certificate_provider_instance()
->set_instance_name("fake_plugin1");
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_default_validation_context()
->add_verify_certificate_spki("spki");
transport_socket->mutable_typed_config()->PackFrom(upstream_tls_context);
balancers_[0]->ads_service()->SetCdsResource(cluster);
ASSERT_TRUE(WaitForCdsNack()) << "timed out waiting for NACK";
const auto response_state =
balancers_[0]->ads_service()->cds_response_state();
EXPECT_EQ(response_state.state, AdsServiceImpl::ResponseState::NACKED);
EXPECT_THAT(
response_state.error_message,
::testing::HasSubstr(
"CertificateValidationContext: verify_certificate_spki unsupported"));
}
TEST_P(XdsSecurityTest,
NacksCertificateValidationContextWithVerifyCertificateHash) {
FakeCertificateProvider::CertDataMap fake1_cert_map = {
{"", {root_cert_, identity_pair_}}};
g_fake1_cert_data_map = &fake1_cert_map;
auto cluster = default_cluster_;
auto* transport_socket = cluster.mutable_transport_socket();
transport_socket->set_name("envoy.transport_sockets.tls");
UpstreamTlsContext upstream_tls_context;
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_validation_context_certificate_provider_instance()
->set_instance_name("fake_plugin1");
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_default_validation_context()
->add_verify_certificate_hash("hash");
transport_socket->mutable_typed_config()->PackFrom(upstream_tls_context);
balancers_[0]->ads_service()->SetCdsResource(cluster);
ASSERT_TRUE(WaitForCdsNack()) << "timed out waiting for NACK";
const auto response_state =
balancers_[0]->ads_service()->cds_response_state();
EXPECT_EQ(response_state.state, AdsServiceImpl::ResponseState::NACKED);
EXPECT_THAT(
response_state.error_message,
::testing::HasSubstr(
"CertificateValidationContext: verify_certificate_hash unsupported"));
}
TEST_P(XdsSecurityTest,
NacksCertificateValidationContextWithRequireSignedCertificateTimes) {
FakeCertificateProvider::CertDataMap fake1_cert_map = {
{"", {root_cert_, identity_pair_}}};
g_fake1_cert_data_map = &fake1_cert_map;
auto cluster = default_cluster_;
auto* transport_socket = cluster.mutable_transport_socket();
transport_socket->set_name("envoy.transport_sockets.tls");
UpstreamTlsContext upstream_tls_context;
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_validation_context_certificate_provider_instance()
->set_instance_name("fake_plugin1");
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_default_validation_context()
->mutable_require_signed_certificate_timestamp()
->set_value(true);
transport_socket->mutable_typed_config()->PackFrom(upstream_tls_context);
balancers_[0]->ads_service()->SetCdsResource(cluster);
ASSERT_TRUE(WaitForCdsNack()) << "timed out waiting for NACK";
const auto response_state =
balancers_[0]->ads_service()->cds_response_state();
EXPECT_EQ(response_state.state, AdsServiceImpl::ResponseState::NACKED);
EXPECT_THAT(
response_state.error_message,
::testing::HasSubstr("CertificateValidationContext: "
"require_signed_certificate_timestamp unsupported"));
}
TEST_P(XdsSecurityTest, NacksCertificateValidationContextWithCrl) {
FakeCertificateProvider::CertDataMap fake1_cert_map = {
{"", {root_cert_, identity_pair_}}};
g_fake1_cert_data_map = &fake1_cert_map;
auto cluster = default_cluster_;
auto* transport_socket = cluster.mutable_transport_socket();
transport_socket->set_name("envoy.transport_sockets.tls");
UpstreamTlsContext upstream_tls_context;
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_validation_context_certificate_provider_instance()
->set_instance_name("fake_plugin1");
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_default_validation_context()
->mutable_crl();
transport_socket->mutable_typed_config()->PackFrom(upstream_tls_context);
balancers_[0]->ads_service()->SetCdsResource(cluster);
ASSERT_TRUE(WaitForCdsNack()) << "timed out waiting for NACK";
const auto response_state =
balancers_[0]->ads_service()->cds_response_state();
EXPECT_EQ(response_state.state, AdsServiceImpl::ResponseState::NACKED);
EXPECT_THAT(
response_state.error_message,
::testing::HasSubstr("CertificateValidationContext: crl unsupported"));
}
TEST_P(XdsSecurityTest,
NacksCertificateValidationContextWithCustomValidatorConfig) {
FakeCertificateProvider::CertDataMap fake1_cert_map = {
{"", {root_cert_, identity_pair_}}};
g_fake1_cert_data_map = &fake1_cert_map;
auto cluster = default_cluster_;
auto* transport_socket = cluster.mutable_transport_socket();
transport_socket->set_name("envoy.transport_sockets.tls");
UpstreamTlsContext upstream_tls_context;
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_validation_context_certificate_provider_instance()
->set_instance_name("fake_plugin1");
upstream_tls_context.mutable_common_tls_context()
->mutable_combined_validation_context()
->mutable_default_validation_context()
->mutable_custom_validator_config();
transport_socket->mutable_typed_config()->PackFrom(upstream_tls_context);
balancers_[0]->ads_service()->SetCdsResource(cluster);
ASSERT_TRUE(WaitForCdsNack()) << "timed out waiting for NACK";
const auto response_state =
balancers_[0]->ads_service()->cds_response_state();
EXPECT_EQ(response_state.state, AdsServiceImpl::ResponseState::NACKED);
EXPECT_THAT(
response_state.error_message,
::testing::HasSubstr(
"CertificateValidationContext: custom_validator_config unsupported"));
}
TEST_P(XdsSecurityTest, TestMtlsConfigurationWithNoSanMatchers) {
FakeCertificateProvider::CertDataMap fake1_cert_map = {
{"", {root_cert_, identity_pair_}}};

Loading…
Cancel
Save