• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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