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