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