xref: /aosp_15_r20/external/grpc-grpc/test/core/end2end/tests/grpc_authz.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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