• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2022 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include "src/core/xds/grpc/xds_metadata.h"
18 
19 #include <google/protobuf/any.pb.h>
20 #include <google/protobuf/struct.pb.h>
21 
22 #include <string>
23 #include <utility>
24 
25 #include "absl/status/status.h"
26 #include "absl/status/statusor.h"
27 #include "absl/strings/str_format.h"
28 #include "envoy/config/core/v3/base.pb.h"
29 #include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h"
30 #include "gmock/gmock.h"
31 #include "gtest/gtest.h"
32 #include "src/core/lib/debug/trace.h"
33 #include "src/core/util/crash.h"
34 #include "src/core/util/json/json.h"
35 #include "src/core/util/json/json_writer.h"
36 #include "src/core/util/ref_counted_ptr.h"
37 #include "src/core/xds/grpc/xds_bootstrap_grpc.h"
38 #include "src/core/xds/grpc/xds_metadata_parser.h"
39 #include "src/core/xds/xds_client/xds_bootstrap.h"
40 #include "src/core/xds/xds_client/xds_client.h"
41 #include "src/core/xds/xds_client/xds_resource_type.h"
42 #include "test/core/test_util/scoped_env_var.h"
43 #include "test/core/test_util/test_config.h"
44 
45 using envoy::config::core::v3::Metadata;
46 using envoy::extensions::filters::http::gcp_authn::v3::Audience;
47 
48 namespace grpc_core {
49 namespace testing {
50 namespace {
51 
52 class XdsMetadataTest : public ::testing::Test {
53  protected:
XdsMetadataTest()54   XdsMetadataTest()
55       : xds_client_(MakeXdsClient()),
56         decode_context_{
57             xds_client_.get(), *xds_client_->bootstrap().servers().front(),
58             &xds_unittest_trace, upb_def_pool_.ptr(), upb_arena_.ptr()} {}
59 
MakeXdsClient()60   static RefCountedPtr<XdsClient> MakeXdsClient() {
61     auto bootstrap = GrpcXdsBootstrap::Create(
62         "{\n"
63         "  \"xds_servers\": [\n"
64         "    {\n"
65         "      \"server_uri\": \"xds.example.com\",\n"
66         "      \"channel_creds\": [\n"
67         "        {\"type\": \"google_default\"}\n"
68         "      ]\n"
69         "    }\n"
70         "  ]\n"
71         "}");
72     if (!bootstrap.ok()) {
73       Crash(absl::StrFormat("Error parsing bootstrap: %s",
74                             bootstrap.status().ToString().c_str()));
75     }
76     return MakeRefCounted<XdsClient>(std::move(*bootstrap),
77                                      /*transport_factory=*/nullptr,
78                                      /*event_engine=*/nullptr,
79                                      /*metrics_reporter=*/nullptr, "foo agent",
80                                      "foo version");
81   }
82 
83   // For convenience, tests build protos using the protobuf API and then
84   // use this function to convert it to a upb object, which can be
85   // passed to ParseXdsMetadataMap() for validation.
ConvertToUpb(Metadata proto)86   const envoy_config_core_v3_Metadata* ConvertToUpb(Metadata proto) {
87     // Serialize the protobuf proto.
88     std::string serialized_proto;
89     if (!proto.SerializeToString(&serialized_proto)) {
90       EXPECT_TRUE(false) << "protobuf serialization failed";
91       return nullptr;
92     }
93     // Deserialize as upb proto.
94     const auto* upb_proto = envoy_config_core_v3_Metadata_parse(
95         serialized_proto.data(), serialized_proto.size(), upb_arena_.ptr());
96     if (upb_proto == nullptr) {
97       EXPECT_TRUE(false) << "upb parsing failed";
98       return nullptr;
99     }
100     return upb_proto;
101   }
102 
Parse(const envoy_config_core_v3_Metadata * upb_proto)103   absl::StatusOr<XdsMetadataMap> Parse(
104       const envoy_config_core_v3_Metadata* upb_proto) {
105     ValidationErrors errors;
106     XdsMetadataMap metadata_map =
107         ParseXdsMetadataMap(decode_context_, upb_proto, &errors);
108     if (!errors.ok()) {
109       return errors.status(absl::StatusCode::kInvalidArgument,
110                            "validation failed");
111     }
112     return metadata_map;
113   }
114 
Decode(Metadata proto)115   absl::StatusOr<XdsMetadataMap> Decode(Metadata proto) {
116     const envoy_config_core_v3_Metadata* upb_proto =
117         ConvertToUpb(std::move(proto));
118     return Parse(upb_proto);
119   }
120 
121   RefCountedPtr<XdsClient> xds_client_;
122   upb::DefPool upb_def_pool_;
123   upb::Arena upb_arena_;
124   XdsResourceType::DecodeContext decode_context_;
125 };
126 
127 MATCHER_P(JsonEq, json_str, "") {
128   std::string actual = JsonDump(arg);
129   bool ok = ::testing::ExplainMatchResult(json_str, actual, result_listener);
130   if (!ok) *result_listener << "Actual: " << actual;
131   return ok;
132 }
133 
TEST_F(XdsMetadataTest,UntypedMetadata)134 TEST_F(XdsMetadataTest, UntypedMetadata) {
135   Metadata metadata_proto;
136   auto& filter_map = *metadata_proto.mutable_filter_metadata();
137   auto& label_map = *filter_map["filter_key"].mutable_fields();
138   *label_map["string_value"].mutable_string_value() = "abc";
139   label_map["bool_value"].set_bool_value(true);
140   label_map["number_value"].set_number_value(3.14);
141   label_map["null_value"].set_null_value(::google::protobuf::NULL_VALUE);
142   auto& list_value_values =
143       *label_map["list_value"].mutable_list_value()->mutable_values();
144   *list_value_values.Add()->mutable_string_value() = "efg";
145   list_value_values.Add()->set_number_value(3.14);
146   auto& struct_value_fields =
147       *label_map["struct_value"].mutable_struct_value()->mutable_fields();
148   struct_value_fields["bool_value"].set_bool_value(false);
149   // Decode.
150   auto metadata_map = Decode(std::move(metadata_proto));
151   ASSERT_TRUE(metadata_map.ok()) << metadata_map.status();
152   ASSERT_EQ(metadata_map->size(), 1);
153   auto* entry = metadata_map->Find("filter_key");
154   ASSERT_NE(entry, nullptr);
155   ASSERT_EQ(XdsStructMetadataValue::Type(), entry->type());
156   EXPECT_THAT(DownCast<const XdsStructMetadataValue*>(entry)->json(),
157               JsonEq("{"
158                      "\"bool_value\":true,"
159                      "\"list_value\":[\"efg\",3.14],"
160                      "\"null_value\":null,"
161                      "\"number_value\":3.14,"
162                      "\"string_value\":\"abc\","
163                      "\"struct_value\":{\"bool_value\":false}"
164                      "}"));
165 }
166 
TEST_F(XdsMetadataTest,TypedMetadataTakesPrecendenceOverUntyped)167 TEST_F(XdsMetadataTest, TypedMetadataTakesPrecendenceOverUntyped) {
168   ScopedExperimentalEnvVar env_var(
169       "GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER");
170   Metadata metadata_proto;
171   auto& filter_map = *metadata_proto.mutable_filter_metadata();
172   auto& label_map = *filter_map["filter_key"].mutable_fields();
173   *label_map["string_value"].mutable_string_value() = "abc";
174   Audience audience_proto;
175   audience_proto.set_url("foo");
176   auto& typed_filter_map = *metadata_proto.mutable_typed_filter_metadata();
177   typed_filter_map["filter_key"].PackFrom(audience_proto);
178   // Decode.
179   auto metadata_map = Decode(std::move(metadata_proto));
180   ASSERT_TRUE(metadata_map.ok()) << metadata_map.status();
181   ASSERT_EQ(metadata_map->size(), 1);
182   auto* entry = metadata_map->Find("filter_key");
183   ASSERT_NE(entry, nullptr);
184   ASSERT_EQ(XdsGcpAuthnAudienceMetadataValue::Type(), entry->type());
185   EXPECT_EQ(DownCast<const XdsGcpAuthnAudienceMetadataValue*>(entry)->url(),
186             "foo");
187 }
188 
TEST_F(XdsMetadataTest,AudienceMetadata)189 TEST_F(XdsMetadataTest, AudienceMetadata) {
190   ScopedExperimentalEnvVar env_var(
191       "GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER");
192   Audience audience_proto;
193   audience_proto.set_url("foo");
194   Metadata metadata_proto;
195   auto& filter_map = *metadata_proto.mutable_typed_filter_metadata();
196   filter_map["filter_key"].PackFrom(audience_proto);
197   // Decode.
198   auto metadata_map = Decode(std::move(metadata_proto));
199   ASSERT_TRUE(metadata_map.ok()) << metadata_map.status();
200   ASSERT_EQ(metadata_map->size(), 1);
201   auto* entry = metadata_map->Find("filter_key");
202   ASSERT_NE(entry, nullptr);
203   ASSERT_EQ(XdsGcpAuthnAudienceMetadataValue::Type(), entry->type());
204   EXPECT_EQ(DownCast<const XdsGcpAuthnAudienceMetadataValue*>(entry)->url(),
205             "foo");
206 }
207 
TEST_F(XdsMetadataTest,AudienceMetadataUnparseable)208 TEST_F(XdsMetadataTest, AudienceMetadataUnparseable) {
209   ScopedExperimentalEnvVar env_var(
210       "GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER");
211   Metadata metadata_proto;
212   auto& filter_map = *metadata_proto.mutable_typed_filter_metadata();
213   auto& entry = filter_map["filter_key"];
214   entry.PackFrom(Audience());
215   entry.set_value(std::string("\0", 1));
216   // Decode.
217   auto metadata_map = Decode(std::move(metadata_proto));
218   EXPECT_EQ(metadata_map.status().code(), absl::StatusCode::kInvalidArgument);
219   EXPECT_EQ(metadata_map.status().message(),
220             "validation failed: ["
221             "field:typed_filter_metadata[filter_key].value["
222             "envoy.extensions.filters.http.gcp_authn.v3.Audience] "
223             "error:could not parse audience metadata]")
224       << metadata_map.status();
225 }
226 
TEST_F(XdsMetadataTest,AudienceMetadataMissingUrl)227 TEST_F(XdsMetadataTest, AudienceMetadataMissingUrl) {
228   ScopedExperimentalEnvVar env_var(
229       "GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER");
230   Metadata metadata_proto;
231   auto& filter_map = *metadata_proto.mutable_typed_filter_metadata();
232   filter_map["filter_key"].PackFrom(Audience());
233   // Decode.
234   auto metadata_map = Decode(std::move(metadata_proto));
235   EXPECT_EQ(metadata_map.status().code(), absl::StatusCode::kInvalidArgument);
236   EXPECT_EQ(metadata_map.status().message(),
237             "validation failed: ["
238             "field:typed_filter_metadata[filter_key].value["
239             "envoy.extensions.filters.http.gcp_authn.v3.Audience].url "
240             "error:must be non-empty]")
241       << metadata_map.status();
242 }
243 
TEST_F(XdsMetadataTest,AudienceIgnoredIfNotEnabled)244 TEST_F(XdsMetadataTest, AudienceIgnoredIfNotEnabled) {
245   Audience audience_proto;
246   audience_proto.set_url("foo");
247   Metadata metadata_proto;
248   auto& filter_map = *metadata_proto.mutable_typed_filter_metadata();
249   filter_map["filter_key"].PackFrom(audience_proto);
250   // Decode.
251   auto metadata_map = Decode(std::move(metadata_proto));
252   ASSERT_TRUE(metadata_map.ok()) << metadata_map.status();
253   EXPECT_EQ(metadata_map->size(), 0);
254 }
255 
TEST_F(XdsMetadataTest,MetadataUnset)256 TEST_F(XdsMetadataTest, MetadataUnset) {
257   auto metadata_map = Parse(nullptr);
258   ASSERT_TRUE(metadata_map.ok()) << metadata_map.status();
259   EXPECT_EQ(metadata_map->size(), 0);
260 }
261 
262 }  // namespace
263 }  // namespace testing
264 }  // namespace grpc_core
265 
main(int argc,char ** argv)266 int main(int argc, char** argv) {
267   ::testing::InitGoogleTest(&argc, argv);
268   grpc::testing::TestEnvironment env(&argc, argv);
269   grpc_init();
270   int ret = RUN_ALL_TESTS();
271   grpc_shutdown();
272   return ret;
273 }
274