1 // Copyright 2021 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <string>
16
17 #include "absl/status/status.h"
18 #include "gtest/gtest.h"
19
20 #include <grpc/grpc.h>
21 #include <grpc/grpc_security.h>
22 #include <grpc/impl/channel_arg_names.h>
23 #include <grpc/status.h>
24 #include <grpc/support/log.h>
25
26 #include "src/core/lib/channel/channel_args.h"
27 #include "src/core/lib/gprpp/notification.h"
28 #include "src/core/lib/gprpp/time.h"
29 #include "src/core/lib/security/authorization/authorization_policy_provider.h"
30 #include "src/core/lib/security/authorization/grpc_authorization_policy_provider.h"
31 #include "test/core/end2end/end2end_tests.h"
32 #include "test/core/util/tls_utils.h"
33
34 namespace grpc_core {
35 namespace {
36
TestAllowAuthorizedRequest(CoreEnd2endTest & test)37 void TestAllowAuthorizedRequest(CoreEnd2endTest& test) {
38 auto c = test.NewClientCall("/foo").Timeout(Duration::Seconds(5)).Create();
39 CoreEnd2endTest::IncomingMetadata server_initial_metadata;
40 CoreEnd2endTest::IncomingStatusOnClient server_status;
41 c.NewBatch(1)
42 .SendInitialMetadata({})
43 .SendCloseFromClient()
44 .RecvInitialMetadata(server_initial_metadata)
45 .RecvStatusOnClient(server_status);
46 auto s = test.RequestCall(101);
47 test.Expect(101, true);
48 test.Step();
49 CoreEnd2endTest::IncomingCloseOnServer client_close;
50 s.NewBatch(102)
51 .SendInitialMetadata({})
52 .SendStatusFromServer(GRPC_STATUS_OK, "xyz", {})
53 .RecvCloseOnServer(client_close);
54 test.Expect(102, true);
55 test.Expect(1, true);
56 test.Step();
57 EXPECT_EQ(server_status.status(), GRPC_STATUS_OK);
58 }
59
TestDenyUnauthorizedRequest(CoreEnd2endTest & test)60 void TestDenyUnauthorizedRequest(CoreEnd2endTest& test) {
61 auto c = test.NewClientCall("/foo").Timeout(Duration::Seconds(5)).Create();
62 CoreEnd2endTest::IncomingMetadata server_initial_metadata;
63 CoreEnd2endTest::IncomingStatusOnClient server_status;
64 c.NewBatch(1)
65 .SendInitialMetadata({})
66 .SendCloseFromClient()
67 .RecvInitialMetadata(server_initial_metadata)
68 .RecvStatusOnClient(server_status);
69 test.Expect(1, true);
70 test.Step();
71 EXPECT_EQ(server_status.status(), GRPC_STATUS_PERMISSION_DENIED);
72 EXPECT_EQ(server_status.message(), "Unauthorized RPC request rejected.");
73 }
74
InitWithPolicy(CoreEnd2endTest & test,grpc_authorization_policy_provider * provider)75 void InitWithPolicy(CoreEnd2endTest& test,
76 grpc_authorization_policy_provider* provider) {
77 test.InitServer(ChannelArgs().Set(
78 GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER,
79 ChannelArgs::Pointer(provider,
80 grpc_authorization_policy_provider_arg_vtable())));
81 test.InitClient(ChannelArgs());
82 }
83
InitWithStaticData(CoreEnd2endTest & test,const char * authz_policy)84 void InitWithStaticData(CoreEnd2endTest& test, const char* authz_policy) {
85 grpc_status_code code = GRPC_STATUS_OK;
86 const char* error_details;
87 grpc_authorization_policy_provider* provider =
88 grpc_authorization_policy_provider_static_data_create(authz_policy, &code,
89 &error_details);
90 EXPECT_EQ(code, GRPC_STATUS_OK);
91 InitWithPolicy(test, provider);
92 }
93
94 class InitWithTempFile {
95 public:
InitWithTempFile(CoreEnd2endTest & test,const char * authz_policy)96 InitWithTempFile(CoreEnd2endTest& test, const char* authz_policy)
97 : tmp_file_(authz_policy) {
98 grpc_status_code code = GRPC_STATUS_OK;
99 const char* error_details;
100 provider_ = grpc_authorization_policy_provider_file_watcher_create(
101 tmp_file_.name().c_str(), /*refresh_interval_sec=*/1, &code,
102 &error_details);
103 GPR_ASSERT(GRPC_STATUS_OK == code);
104 InitWithPolicy(test, provider_);
105 }
106
107 InitWithTempFile(const InitWithTempFile&) = delete;
108 InitWithTempFile& operator=(const InitWithTempFile&) = delete;
109
provider()110 FileWatcherAuthorizationPolicyProvider* provider() {
111 return dynamic_cast<FileWatcherAuthorizationPolicyProvider*>(provider_);
112 }
113
file()114 testing::TmpFile& file() { return tmp_file_; }
115
116 private:
117 testing::TmpFile tmp_file_;
118 grpc_authorization_policy_provider* provider_;
119 };
120
CORE_END2END_TEST(SecureEnd2endTest,StaticInitAllowAuthorizedRequest)121 CORE_END2END_TEST(SecureEnd2endTest, StaticInitAllowAuthorizedRequest) {
122 InitWithStaticData(*this,
123 "{"
124 " \"name\": \"authz\","
125 " \"allow_rules\": ["
126 " {"
127 " \"name\": \"allow_foo\","
128 " \"request\": {"
129 " \"paths\": ["
130 " \"*/foo\""
131 " ]"
132 " }"
133 " }"
134 " ]"
135 "}");
136 TestAllowAuthorizedRequest(*this);
137 }
138
CORE_END2END_TEST(SecureEnd2endTest,StaticInitDenyUnauthorizedRequest)139 CORE_END2END_TEST(SecureEnd2endTest, StaticInitDenyUnauthorizedRequest) {
140 InitWithStaticData(*this,
141 "{"
142 " \"name\": \"authz\","
143 " \"allow_rules\": ["
144 " {"
145 " \"name\": \"allow_bar\","
146 " \"request\": {"
147 " \"paths\": ["
148 " \"*/bar\""
149 " ]"
150 " }"
151 " }"
152 " ],"
153 " \"deny_rules\": ["
154 " {"
155 " \"name\": \"deny_foo\","
156 " \"request\": {"
157 " \"paths\": ["
158 " \"*/foo\""
159 " ]"
160 " }"
161 " }"
162 " ]"
163 "}");
164 TestDenyUnauthorizedRequest(*this);
165 }
166
CORE_END2END_TEST(SecureEnd2endTest,StaticInitDenyRequestNoMatchInPolicy)167 CORE_END2END_TEST(SecureEnd2endTest, StaticInitDenyRequestNoMatchInPolicy) {
168 InitWithStaticData(*this,
169 "{"
170 " \"name\": \"authz\","
171 " \"allow_rules\": ["
172 " {"
173 " \"name\": \"allow_bar\","
174 " \"request\": {"
175 " \"paths\": ["
176 " \"*/bar\""
177 " ]"
178 " }"
179 " }"
180 " ]"
181 "}");
182 TestDenyUnauthorizedRequest(*this);
183 }
184
CORE_END2END_TEST(SecureEnd2endTest,FileWatcherInitAllowAuthorizedRequest)185 CORE_END2END_TEST(SecureEnd2endTest, FileWatcherInitAllowAuthorizedRequest) {
186 InitWithTempFile tmp_policy(*this,
187 "{"
188 " \"name\": \"authz\","
189 " \"allow_rules\": ["
190 " {"
191 " \"name\": \"allow_foo\","
192 " \"request\": {"
193 " \"paths\": ["
194 " \"*/foo\""
195 " ]"
196 " }"
197 " }"
198 " ]"
199 "}");
200 TestAllowAuthorizedRequest(*this);
201 }
202
CORE_END2END_TEST(SecureEnd2endTest,FileWatcherInitDenyUnauthorizedRequest)203 CORE_END2END_TEST(SecureEnd2endTest, FileWatcherInitDenyUnauthorizedRequest) {
204 InitWithTempFile tmp_policy(*this,
205 "{"
206 " \"name\": \"authz\","
207 " \"allow_rules\": ["
208 " {"
209 " \"name\": \"allow_bar\","
210 " \"request\": {"
211 " \"paths\": ["
212 " \"*/bar\""
213 " ]"
214 " }"
215 " }"
216 " ],"
217 " \"deny_rules\": ["
218 " {"
219 " \"name\": \"deny_foo\","
220 " \"request\": {"
221 " \"paths\": ["
222 " \"*/foo\""
223 " ]"
224 " }"
225 " }"
226 " ]"
227 "}");
228 TestDenyUnauthorizedRequest(*this);
229 }
230
CORE_END2END_TEST(SecureEnd2endTest,FileWatcherInitDenyRequestNoMatchInPolicy)231 CORE_END2END_TEST(SecureEnd2endTest,
232 FileWatcherInitDenyRequestNoMatchInPolicy) {
233 InitWithTempFile tmp_policy(*this,
234 "{"
235 " \"name\": \"authz\","
236 " \"allow_rules\": ["
237 " {"
238 " \"name\": \"allow_bar\","
239 " \"request\": {"
240 " \"paths\": ["
241 " \"*/bar\""
242 " ]"
243 " }"
244 " }"
245 " ]"
246 "}");
247 TestDenyUnauthorizedRequest(*this);
248 }
249
CORE_END2END_TEST(SecureEnd2endTest,FileWatcherValidPolicyReload)250 CORE_END2END_TEST(SecureEnd2endTest, FileWatcherValidPolicyReload) {
251 InitWithTempFile tmp_policy(*this,
252 "{"
253 " \"name\": \"authz\","
254 " \"allow_rules\": ["
255 " {"
256 " \"name\": \"allow_foo\","
257 " \"request\": {"
258 " \"paths\": ["
259 " \"*/foo\""
260 " ]"
261 " }"
262 " }"
263 " ]"
264 "}");
265 TestAllowAuthorizedRequest(*this);
266 Notification on_reload_done;
267 tmp_policy.provider()->SetCallbackForTesting(
268 [&on_reload_done](bool contents_changed, absl::Status status) {
269 if (contents_changed) {
270 EXPECT_EQ(status, absl::OkStatus());
271 on_reload_done.Notify();
272 }
273 });
274 // Replace existing policy in file with a different authorization policy.
275 tmp_policy.file().RewriteFile(
276 "{"
277 " \"name\": \"authz\","
278 " \"allow_rules\": ["
279 " {"
280 " \"name\": \"allow_bar\","
281 " \"request\": {"
282 " \"paths\": ["
283 " \"*/bar\""
284 " ]"
285 " }"
286 " }"
287 " ],"
288 " \"deny_rules\": ["
289 " {"
290 " \"name\": \"deny_foo\","
291 " \"request\": {"
292 " \"paths\": ["
293 " \"*/foo\""
294 " ]"
295 " }"
296 " }"
297 " ]"
298 "}");
299 on_reload_done.WaitForNotification();
300 TestDenyUnauthorizedRequest(*this);
301 tmp_policy.provider()->SetCallbackForTesting(nullptr);
302 }
303
CORE_END2END_TEST(SecureEnd2endTest,FileWatcherInvalidPolicySkipReload)304 CORE_END2END_TEST(SecureEnd2endTest, FileWatcherInvalidPolicySkipReload) {
305 InitWithTempFile tmp_policy(*this,
306 "{"
307 " \"name\": \"authz\","
308 " \"allow_rules\": ["
309 " {"
310 " \"name\": \"allow_foo\","
311 " \"request\": {"
312 " \"paths\": ["
313 " \"*/foo\""
314 " ]"
315 " }"
316 " }"
317 " ]"
318 "}");
319 TestAllowAuthorizedRequest(*this);
320 Notification on_reload_done;
321 tmp_policy.provider()->SetCallbackForTesting(
322 [&on_reload_done](bool contents_changed, absl::Status status) {
323 if (contents_changed) {
324 EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
325 EXPECT_EQ(status.message(), "\"name\" field is not present.");
326 on_reload_done.Notify();
327 }
328 });
329 // Replace exisiting policy in file with an invalid policy.
330 tmp_policy.file().RewriteFile("{}");
331 on_reload_done.WaitForNotification();
332 TestAllowAuthorizedRequest(*this);
333 tmp_policy.provider()->SetCallbackForTesting(nullptr);
334 }
335
CORE_END2END_TEST(SecureEnd2endTest,FileWatcherRecoversFromFailure)336 CORE_END2END_TEST(SecureEnd2endTest, FileWatcherRecoversFromFailure) {
337 InitWithTempFile tmp_policy(*this,
338 "{"
339 " \"name\": \"authz\","
340 " \"allow_rules\": ["
341 " {"
342 " \"name\": \"allow_foo\","
343 " \"request\": {"
344 " \"paths\": ["
345 " \"*/foo\""
346 " ]"
347 " }"
348 " }"
349 " ]"
350 "}");
351 TestAllowAuthorizedRequest(*this);
352 Notification on_first_reload_done;
353 tmp_policy.provider()->SetCallbackForTesting(
354 [&on_first_reload_done](bool contents_changed, absl::Status status) {
355 if (contents_changed) {
356 EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
357 EXPECT_EQ(status.message(), "\"name\" field is not present.");
358 on_first_reload_done.Notify();
359 }
360 });
361 // Replace exisiting policy in file with an invalid policy.
362 tmp_policy.file().RewriteFile("{}");
363 on_first_reload_done.WaitForNotification();
364 TestAllowAuthorizedRequest(*this);
365 Notification on_second_reload_done;
366 tmp_policy.provider()->SetCallbackForTesting(
367 [&on_second_reload_done](bool contents_changed, absl::Status status) {
368 if (contents_changed) {
369 EXPECT_EQ(status, absl::OkStatus());
370 on_second_reload_done.Notify();
371 }
372 });
373 // Recover from reload errors, by replacing invalid policy in file with a
374 // valid policy.
375 tmp_policy.file().RewriteFile(
376 "{"
377 " \"name\": \"authz\","
378 " \"allow_rules\": ["
379 " {"
380 " \"name\": \"allow_bar\","
381 " \"request\": {"
382 " \"paths\": ["
383 " \"*/bar\""
384 " ]"
385 " }"
386 " }"
387 " ],"
388 " \"deny_rules\": ["
389 " {"
390 " \"name\": \"deny_foo\","
391 " \"request\": {"
392 " \"paths\": ["
393 " \"*/foo\""
394 " ]"
395 " }"
396 " }"
397 " ]"
398 "}");
399 on_second_reload_done.WaitForNotification();
400 TestDenyUnauthorizedRequest(*this);
401 tmp_policy.provider()->SetCallbackForTesting(nullptr);
402 }
403
404 } // namespace
405 } // namespace grpc_core
406