mirror of https://github.com/grpc/grpc.git
parent
befc7a7d4b
commit
c9e99945ec
35 changed files with 1292 additions and 177 deletions
@ -0,0 +1,91 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2020 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.
|
||||
//
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/security/credentials/tls/tls_utils.h" |
||||
|
||||
#include "absl/strings/ascii.h" |
||||
#include "absl/strings/match.h" |
||||
#include "absl/strings/str_cat.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// Based on
|
||||
// https://github.com/grpc/grpc-java/blob/ca12e7a339add0ef48202fb72434b9dc0df41756/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java#L62
|
||||
bool VerifySubjectAlternativeName(absl::string_view subject_alternative_name, |
||||
const std::string& matcher) { |
||||
if (subject_alternative_name.empty() || |
||||
absl::StartsWith(subject_alternative_name, ".")) { |
||||
// Illegal pattern/domain name
|
||||
return false; |
||||
} |
||||
if (matcher.empty() || absl::StartsWith(matcher, ".")) { |
||||
// Illegal domain name
|
||||
return false; |
||||
} |
||||
// Normalize \a subject_alternative_name and \a matcher by turning them into
|
||||
// absolute domain names if they are not yet absolute. This is needed because
|
||||
// server certificates do not normally contain absolute names or patterns, but
|
||||
// they should be treated as absolute. At the same time, any
|
||||
// subject_alternative_name presented to this method should also be treated as
|
||||
// absolute for the purposes of matching to the server certificate.
|
||||
std::string normalized_san = |
||||
absl::EndsWith(subject_alternative_name, ".") |
||||
? std::string(subject_alternative_name) |
||||
: absl::StrCat(subject_alternative_name, "."); |
||||
std::string normalized_matcher = |
||||
absl::EndsWith(matcher, ".") ? matcher : absl::StrCat(matcher, "."); |
||||
absl::AsciiStrToLower(&normalized_san); |
||||
absl::AsciiStrToLower(&normalized_matcher); |
||||
if (!absl::StrContains(normalized_san, "*")) { |
||||
return normalized_san == normalized_matcher; |
||||
} |
||||
// WILDCARD PATTERN RULES:
|
||||
// 1. Asterisk (*) is only permitted in the left-most domain name label and
|
||||
// must be the only character in that label (i.e., must match the whole
|
||||
// left-most label). For example, *.example.com is permitted, while
|
||||
// *a.example.com, a*.example.com, a*b.example.com, a.*.example.com are
|
||||
// not permitted.
|
||||
// 2. Asterisk (*) cannot match across domain name labels.
|
||||
// For example, *.example.com matches test.example.com but does not match
|
||||
// sub.test.example.com.
|
||||
// 3. Wildcard patterns for single-label domain names are not permitted.
|
||||
if (!absl::StartsWith(normalized_san, "*.")) { |
||||
// Asterisk (*) is only permitted in the left-most domain name label and
|
||||
// must be the only character in that label
|
||||
return false; |
||||
} |
||||
if (normalized_san == "*.") { |
||||
// Wildcard pattern for single-label domain name -- not permitted.
|
||||
return false; |
||||
} |
||||
absl::string_view suffix = absl::string_view(normalized_san).substr(1); |
||||
if (absl::StrContains(suffix, "*")) { |
||||
// Asterisk (*) is not permitted in the suffix
|
||||
return false; |
||||
} |
||||
if (!absl::EndsWith(normalized_matcher, suffix)) return false; |
||||
int suffix_start_index = normalized_matcher.length() - suffix.length(); |
||||
// Asterisk matching across domain labels is not permitted.
|
||||
return suffix_start_index <= 0 /* should not happen */ || |
||||
normalized_matcher.find_last_of('.', suffix_start_index - 1) == |
||||
std::string::npos; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,38 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2020 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.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_LIB_SECURITY_CREDENTIALS_TLS_TLS_UTILS_H |
||||
#define GRPC_CORE_LIB_SECURITY_CREDENTIALS_TLS_TLS_UTILS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// Matches \a subject_alternative_name with \a matcher. Returns true if there
|
||||
// is a match, false otherwise.
|
||||
bool VerifySubjectAlternativeName(absl::string_view subject_alternative_name, |
||||
const std::string& matcher); |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_SECURITY_CREDENTIALS_TLS_TLS_UTILS_H
|
@ -0,0 +1,306 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2020 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.
|
||||
//
|
||||
//
|
||||
|
||||
#include "src/core/lib/security/credentials/xds/xds_credentials.h" |
||||
|
||||
#include <gtest/gtest.h> |
||||
|
||||
#include <grpc/grpc.h> |
||||
|
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
|
||||
namespace { |
||||
|
||||
XdsApi::StringMatcher ExactMatcher(const char* string) { |
||||
return XdsApi::StringMatcher(XdsApi::StringMatcher::StringMatcherType::EXACT, |
||||
string); |
||||
} |
||||
|
||||
XdsApi::StringMatcher PrefixMatcher(const char* string, |
||||
bool ignore_case = false) { |
||||
return XdsApi::StringMatcher(XdsApi::StringMatcher::StringMatcherType::PREFIX, |
||||
string, ignore_case); |
||||
} |
||||
|
||||
XdsApi::StringMatcher SuffixMatcher(const char* string, |
||||
bool ignore_case = false) { |
||||
return XdsApi::StringMatcher(XdsApi::StringMatcher::StringMatcherType::SUFFIX, |
||||
string, ignore_case); |
||||
} |
||||
|
||||
XdsApi::StringMatcher ContainsMatcher(const char* string, |
||||
bool ignore_case = false) { |
||||
return XdsApi::StringMatcher( |
||||
XdsApi::StringMatcher::StringMatcherType::CONTAINS, string, ignore_case); |
||||
} |
||||
|
||||
XdsApi::StringMatcher SafeRegexMatcher(const char* string) { |
||||
return XdsApi::StringMatcher( |
||||
XdsApi::StringMatcher::StringMatcherType::SAFE_REGEX, string); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, EmptySansList) { |
||||
std::vector<const char*> sans = {}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ExactMatcher("a.example.com"), ExactMatcher("b.example.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, EmptyMatchersList) { |
||||
std::vector<const char*> sans = {"a.example.com", "foo.example.com"}; |
||||
EXPECT_TRUE( |
||||
TestOnlyXdsVerifySubjectAlternativeNames(sans.data(), sans.size(), {})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchIllegalValues) { |
||||
std::vector<const char*> sans = {".a.example.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ExactMatcher(""), ExactMatcher("a.example.com"), |
||||
ExactMatcher(".a.example.com")})); |
||||
sans = {""}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ExactMatcher(""), ExactMatcher("a.example.com"), |
||||
ExactMatcher(".a.example.com")})); |
||||
sans = {"a.example.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ExactMatcher(""), ExactMatcher("a.example.com"), |
||||
ExactMatcher(".a.example.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchDns) { |
||||
std::vector<const char*> sans = {"a.example.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("a.example.com")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("b.example.com")})); |
||||
sans = {"b.example.com."}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("a.example.com.")})); |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("b.example.com.")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchWithFullyQualifiedSan) { |
||||
std::vector<const char*> sans = {"a.example.com."}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("a.example.com")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("b.example.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchWithFullyQualifiedMatcher) { |
||||
std::vector<const char*> sans = {"a.example.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("a.example.com.")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("b.example.com.")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchDnsCaseInsensitive) { |
||||
std::vector<const char*> sans = {"A.eXaMpLe.CoM"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("a.example.com")})); |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("a.ExAmPlE.cOm")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchMultipleSansMultipleMatchers) { |
||||
std::vector<const char*> sans = {"a.example.com", "foo.example.com", |
||||
"b.example.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ExactMatcher("abc.example.com"), ExactMatcher("foo.example.com"), |
||||
ExactMatcher("xyz.example.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchWildCard) { |
||||
std::vector<const char*> sans = {"*.example.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("a.example.com")})); |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("fOo.ExAmPlE.cOm")})); |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("BaR.eXaMpLe.CoM")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher(".example.com")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("example.com")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("foo.bar.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchWildCardDoesNotMatchSingleLabelDomain) { |
||||
std::vector<const char*> sans = {"*"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc.com.")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("bar.baz.com")})); |
||||
sans = {"*."}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc.com.")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("bar.baz.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ExactMatchAsteriskOnlyPermittedInLeftMostDomainName) { |
||||
std::vector<const char*> sans = {"*.example.*.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc.example.xyz.com")})); |
||||
sans = {"*.exam*ple.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc.example.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, |
||||
ExactMatchAsteriskMustBeOnlyCharacterInLeftMostDomainName) { |
||||
std::vector<const char*> sans = {"*c.example.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc.example.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, |
||||
ExactMatchAsteriskMatchingAcrossDomainLabelsNotPermitted) { |
||||
std::vector<const char*> sans = {"*.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc.example.com")})); |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("foo.bar.baz.com")})); |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ExactMatcher("abc.com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, PrefixMatch) { |
||||
std::vector<const char*> sans = {"abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames(sans.data(), sans.size(), |
||||
{PrefixMatcher("abc")})); |
||||
sans = {"AbC.CoM"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {PrefixMatcher("abc")})); |
||||
sans = {"xyz.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {PrefixMatcher("abc")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, PrefixMatchIgnoreCase) { |
||||
std::vector<const char*> sans = {"aBc.cOm"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{PrefixMatcher("AbC", true /* ignore_case */)})); |
||||
sans = {"abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{PrefixMatcher("AbC", true /* ignore_case */)})); |
||||
sans = {"xyz.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{PrefixMatcher("AbC", true /* ignore_case */)})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, SuffixMatch) { |
||||
std::vector<const char*> sans = {"abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {SuffixMatcher(".com")})); |
||||
sans = {"AbC.CoM"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {SuffixMatcher(".com")})); |
||||
sans = {"abc.xyz"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {SuffixMatcher(".com")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, SuffixMatchIgnoreCase) { |
||||
std::vector<const char*> sans = {"abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{SuffixMatcher(".CoM", true /* ignore_case */)})); |
||||
sans = {"AbC.cOm"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{SuffixMatcher(".CoM", true /* ignore_case */)})); |
||||
sans = {"abc.xyz"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{SuffixMatcher(".CoM", true /* ignore_case */)})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ContainsMatch) { |
||||
std::vector<const char*> sans = {"abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ContainsMatcher("abc")})); |
||||
sans = {"xyz.abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ContainsMatcher("abc")})); |
||||
sans = {"foo.AbC.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {ContainsMatcher("abc")})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, ContainsMatchIgnoresCase) { |
||||
std::vector<const char*> sans = {"abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ContainsMatcher("AbC", true /* ignore_case */)})); |
||||
sans = {"xyz.abc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ContainsMatcher("AbC", true /* ignore_case */)})); |
||||
sans = {"foo.aBc.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ContainsMatcher("AbC", true /* ignore_case */)})); |
||||
sans = {"foo.Ab.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), |
||||
{ContainsMatcher("AbC", true /* ignore_case */)})); |
||||
} |
||||
|
||||
TEST(XdsSanMatchingTest, RegexMatch) { |
||||
std::vector<const char*> sans = {"abc.example.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {SafeRegexMatcher("(abc|xyz).example.com")})); |
||||
sans = {"xyz.example.com"}; |
||||
EXPECT_TRUE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {SafeRegexMatcher("(abc|xyz).example.com")})); |
||||
sans = {"foo.example.com"}; |
||||
EXPECT_FALSE(TestOnlyXdsVerifySubjectAlternativeNames( |
||||
sans.data(), sans.size(), {SafeRegexMatcher("(abc|xyz).example.com")})); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(argc, argv); |
||||
grpc_init(); |
||||
auto result = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
return result; |
||||
} |
Loading…
Reference in new issue