// Copyright 2021 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include "src/core/lib/security/authorization/audit_logging.h" #include "src/core/lib/security/authorization/grpc_authorization_policy_provider.h" #include "src/core/lib/security/credentials/fake/fake_credentials.h" #include "src/cpp/client/secure_credentials.h" #include "src/cpp/server/secure_server_credentials.h" #include "src/proto/grpc/testing/echo.grpc.pb.h" #include "test/core/util/audit_logging_utils.h" #include "test/core/util/port.h" #include "test/core/util/test_config.h" #include "test/core/util/tls_utils.h" #include "test/cpp/end2end/test_service_impl.h" namespace grpc { namespace testing { namespace { constexpr char kCaCertPath[] = "src/core/tsi/test_creds/ca.pem"; constexpr char kServerCertPath[] = "src/core/tsi/test_creds/server1.pem"; constexpr char kServerKeyPath[] = "src/core/tsi/test_creds/server1.key"; constexpr char kClientCertPath[] = "src/core/tsi/test_creds/client-with-spiffe.pem"; constexpr char kClientKeyPath[] = "src/core/tsi/test_creds/client-with-spiffe.key"; constexpr char kMessage[] = "Hello"; using experimental::RegisterAuditLoggerFactory; using grpc_core::experimental::AuditLoggerRegistry; using grpc_core::testing::TestAuditLoggerFactory; class GrpcAuthzEnd2EndTest : public ::testing::Test { protected: GrpcAuthzEnd2EndTest() : server_address_( absl::StrCat("localhost:", grpc_pick_unused_port_or_die())) { std::string root_cert = grpc_core::testing::GetFileContents(kCaCertPath); std::string identity_cert = grpc_core::testing::GetFileContents(kServerCertPath); std::string private_key = grpc_core::testing::GetFileContents(kServerKeyPath); std::vector server_identity_key_cert_pairs = {{private_key, identity_cert}}; grpc::experimental::TlsServerCredentialsOptions server_options( std::make_shared( root_cert, server_identity_key_cert_pairs)); server_options.watch_root_certs(); server_options.watch_identity_key_cert_pairs(); server_options.set_cert_request_type( GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY); server_creds_ = grpc::experimental::TlsServerCredentials(server_options); std::vector channel_identity_key_cert_pairs = { {grpc_core::testing::GetFileContents(kClientKeyPath), grpc_core::testing::GetFileContents(kClientCertPath)}}; grpc::experimental::TlsChannelCredentialsOptions channel_options; channel_options.set_certificate_provider( std::make_shared( grpc_core::testing::GetFileContents(kCaCertPath), channel_identity_key_cert_pairs)); channel_options.watch_identity_key_cert_pairs(); channel_options.watch_root_certs(); channel_creds_ = grpc::experimental::TlsCredentials(channel_options); RegisterAuditLoggerFactory( std::make_unique(&audit_logs_)); } ~GrpcAuthzEnd2EndTest() override { AuditLoggerRegistry::TestOnlyResetRegistry(); server_->Shutdown(); } // Replaces existing credentials with insecure credentials. void UseInsecureCredentials() { server_creds_ = InsecureServerCredentials(); channel_creds_ = InsecureChannelCredentials(); } // Creates server with gRPC authorization enabled when provider is not null. void InitServer( std::shared_ptr provider) { ServerBuilder builder; builder.AddListeningPort(server_address_, std::move(server_creds_)); builder.experimental().SetAuthorizationPolicyProvider(std::move(provider)); builder.RegisterService(&service_); server_ = builder.BuildAndStart(); } std::shared_ptr CreateStaticAuthzPolicyProvider(const std::string& policy) { grpc::Status status; auto provider = experimental::StaticDataAuthorizationPolicyProvider::Create( policy, &status); EXPECT_TRUE(status.ok()); return provider; } std::shared_ptr CreateFileWatcherAuthzPolicyProvider(const std::string& policy_path, unsigned int refresh_interval_sec) { grpc::Status status; auto provider = experimental::FileWatcherAuthorizationPolicyProvider::Create( policy_path, refresh_interval_sec, &status); EXPECT_TRUE(status.ok()); return provider; } std::shared_ptr BuildChannel() { ChannelArguments args; // Override target name for host name check args.SetSslTargetNameOverride("foo.test.google.fr"); return grpc::CreateCustomChannel(server_address_, channel_creds_, args); } grpc::Status SendRpc(const std::shared_ptr& channel, ClientContext* context, grpc::testing::EchoResponse* response = nullptr) { auto stub = grpc::testing::EchoTestService::NewStub(channel); grpc::testing::EchoRequest request; request.set_message(kMessage); return stub->Echo(context, request, response); } std::string server_address_; TestServiceImpl service_; std::unique_ptr server_; std::shared_ptr server_creds_; std::shared_ptr channel_creds_; std::vector audit_logs_; }; TEST_F(GrpcAuthzEnd2EndTest, StaticInitAllowsRpcRequestNoMatchInDenyMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]," " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo1\", \"foo2\"]" " }," " {" " \"key\": \"key-bar\"," " \"values\": [\"bar1\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_clientstreamingecho\"," " \"request\": {" " \"paths\": [" " \"*/ClientStreamingEcho\"" " ]" " }" " }" " ]" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; context.AddMetadata("key-foo", "foo2"); context.AddMetadata("key-bar", "bar1"); context.AddMetadata("key-baz", "baz1"); grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp.message(), kMessage); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitDeniesRpcRequestNoMatchInAllowAndDeny) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"paths\": [" " \"*/foo\"" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"source\": {" " \"principals\": [" " \"bar\"" " ]" " }" " }" " ]" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitDeniesRpcRequestMatchInDenyMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_all\"" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitDeniesRpcRequestMatchInDenyNoMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_clientstreamingecho\"," " \"request\": {" " \"paths\": [" " \"*/ClientStreamingEcho\"" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitAllowsRpcRequestEmptyDenyMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*\"" " ]," " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo1\", \"foo2\"]" " }," " {" " \"key\": \"key-bar\"," " \"values\": [\"bar1\"]" " }" " ]" " }" " }" " ]" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; context.AddMetadata("key-foo", "foo2"); context.AddMetadata("key-bar", "bar1"); context.AddMetadata("key-baz", "baz1"); grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp.message(), kMessage); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitDeniesRpcRequestEmptyDenyNoMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]," " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo1\"]" " }" " ]" " }" " }" " ]" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; context.AddMetadata("key-bar", "bar1"); grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F( GrpcAuthzEnd2EndTest, StaticInitDeniesRpcRequestWithPrincipalsFieldOnUnauthenticatedConnection) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_mtls\"," " \"source\": {" " \"principals\": [\"*\"]" " }" " }" " ]" "}"; UseInsecureCredentials(); InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitAllowsRpcRequestWithPrincipalsFieldOnAuthenticatedConnection) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_mtls\"," " \"source\": {" " \"principals\": [\"*\"]" " }" " }" " ]" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp.message(), kMessage); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInAllowWithAuditLoggingNone) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"NONE\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the allow rule. context.AddMetadata("key-foo", "foo"); status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_EQ(audit_logs_.size(), 0); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitNoMatchWithAuditLoggingNone) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"NONE\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Does not match the allow rule or deny rule. context.AddMetadata("key-foo", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_EQ(audit_logs_.size(), 0); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInDenyWithAuditLoggingNone) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"NONE\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the deny rule. context.AddMetadata("key-bar", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_EQ(audit_logs_.size(), 0); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInAllowWithAuditLoggingOnDeny) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_DENY\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the allow rule. context.AddMetadata("key-foo", "foo"); status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_EQ(audit_logs_.size(), 0); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitNoMatchWithAuditLoggingOnDeny) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_DENY\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Does not match the allow rule or deny rule. context.AddMetadata("key-foo", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":false,\"matched_rule\":\"\",\"policy_name\":" "\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInDenyWithAuditLoggingOnDeny) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_DENY\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the deny rule. context.AddMetadata("key-bar", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":false,\"matched_rule\":\"deny_bar\",\"policy_name\":" "\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInAllowWithAuditLoggingOnAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_ALLOW\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the allow rule. context.AddMetadata("key-foo", "foo"); status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":true,\"matched_rule\":\"allow_foo\",\"policy_" "name\":\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitNoMatchWithAuditLoggingOnAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_ALLOW\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Does not match the allow rule. No audit log emitted. context.AddMetadata("key-foo", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_EQ(audit_logs_.size(), 0); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInDenyWithAuditLoggingOnAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_ALLOW\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the deny rule. No audit log emitted. context.AddMetadata("key-bar", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_EQ(audit_logs_.size(), 0); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInAllowWithAuditLoggingOnDenyAndAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_DENY_AND_ALLOW\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the allow rule. context.AddMetadata("key-foo", "foo"); status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":true,\"matched_rule\":\"allow_foo\",\"policy_" "name\":\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitNoMatchWithAuditLoggingOnDenyAndAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_DENY_AND_ALLOW\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Does not match the allow rule. context.AddMetadata("key-foo", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":false,\"matched_rule\":\"\",\"policy_" "name\":\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); } TEST_F(GrpcAuthzEnd2EndTest, StaticInitMatchInDenyWithAuditLoggingOnDenyAndAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"request\": {" " \"headers\": [" " {" " \"key\": \"key-bar\"," " \"values\": [\"bar\"]" " }" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_DENY_AND_ALLOW\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; InitServer(CreateStaticAuthzPolicyProvider(policy)); auto channel = BuildChannel(); grpc::testing::EchoResponse resp; grpc::Status status; ClientContext context; // Matches the deny rule. context.AddMetadata("key-bar", "bar"); status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":false,\"matched_rule\":\"deny_bar\",\"policy_" "name\":\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherInitAllowsRpcRequestNoMatchInDenyMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]," " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo1\", \"foo2\"]" " }," " {" " \"key\": \"key-bar\"," " \"values\": [\"bar1\"]" " }" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_clientstreamingecho\"," " \"request\": {" " \"paths\": [" " \"*/ClientStreamingEcho\"" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 5)); auto channel = BuildChannel(); ClientContext context; context.AddMetadata("key-foo", "foo2"); context.AddMetadata("key-bar", "bar1"); context.AddMetadata("key-baz", "baz1"); grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp.message(), kMessage); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherInitDeniesRpcRequestNoMatchInAllowAndDeny) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"paths\": [" " \"*/foo\"" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_bar\"," " \"source\": {" " \"principals\": [" " \"bar\"" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 5)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherInitDeniesRpcRequestMatchInDenyMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_all\"" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 5)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherInitDeniesRpcRequestMatchInDenyNoMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_clientstreamingecho\"," " \"request\": {" " \"paths\": [" " \"*/ClientStreamingEcho\"" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 5)); auto channel = BuildChannel(); ClientContext context; grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherInitAllowsRpcRequestEmptyDenyMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]," " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo1\", \"foo2\"]" " }," " {" " \"key\": \"key-bar\"," " \"values\": [\"bar1\"]" " }" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 5)); auto channel = BuildChannel(); ClientContext context; context.AddMetadata("key-foo", "foo2"); context.AddMetadata("key-bar", "bar1"); context.AddMetadata("key-baz", "baz1"); grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp.message(), kMessage); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherInitDeniesRpcRequestEmptyDenyNoMatchInAllow) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]," " \"headers\": [" " {" " \"key\": \"key-foo\"," " \"values\": [\"foo1\"]" " }" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 5)); auto channel = BuildChannel(); ClientContext context; context.AddMetadata("key-bar", "bar1"); grpc::testing::EchoResponse resp; grpc::Status status = SendRpc(channel, &context, &resp); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp.message().empty()); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherValidPolicyRefresh) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); auto provider = CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 1); InitServer(provider); auto channel = BuildChannel(); ClientContext context1; grpc::testing::EchoResponse resp1; grpc::Status status = SendRpc(channel, &context1, &resp1); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp1.message(), kMessage); gpr_event on_reload_done; gpr_event_init(&on_reload_done); std::function callback = [&on_reload_done](bool contents_changed, absl::Status status) { if (contents_changed) { EXPECT_TRUE(status.ok()); gpr_event_set(&on_reload_done, reinterpret_cast(1)); } }; dynamic_cast( provider->c_provider()) ->SetCallbackForTesting(std::move(callback)); // Replace the existing policy with a new authorization policy. policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"paths\": [" " \"*/foo\"" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]" "}"; tmp_policy.RewriteFile(policy); // Wait for the provider's refresh thread to read the updated files. ASSERT_EQ( gpr_event_wait(&on_reload_done, gpr_inf_future(GPR_CLOCK_MONOTONIC)), reinterpret_cast(1)); ClientContext context2; grpc::testing::EchoResponse resp2; status = SendRpc(channel, &context2, &resp2); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp2.message().empty()); dynamic_cast( provider->c_provider()) ->SetCallbackForTesting(nullptr); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherInvalidPolicyRefreshSkipsReload) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]" "}"; grpc_core::testing::TmpFile tmp_policy(policy); auto provider = CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 1); InitServer(provider); auto channel = BuildChannel(); ClientContext context1; grpc::testing::EchoResponse resp1; grpc::Status status = SendRpc(channel, &context1, &resp1); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp1.message(), kMessage); gpr_event on_reload_done; gpr_event_init(&on_reload_done); std::function callback = [&on_reload_done](bool contents_changed, absl::Status status) { if (contents_changed) { EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); EXPECT_EQ(status.message(), "\"name\" field is not present."); gpr_event_set(&on_reload_done, reinterpret_cast(1)); } }; dynamic_cast( provider->c_provider()) ->SetCallbackForTesting(std::move(callback)); // Replaces existing policy with an invalid authorization policy. policy = "{}"; tmp_policy.RewriteFile(policy); // Wait for the provider's refresh thread to read the updated files. ASSERT_EQ( gpr_event_wait(&on_reload_done, gpr_inf_future(GPR_CLOCK_MONOTONIC)), reinterpret_cast(1)); ClientContext context2; grpc::testing::EchoResponse resp2; status = SendRpc(channel, &context2, &resp2); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp2.message(), kMessage); dynamic_cast( provider->c_provider()) ->SetCallbackForTesting(nullptr); } TEST_F(GrpcAuthzEnd2EndTest, FileWatcherWithAuditLoggingRecoversFromFailure) { std::string policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_ALLOW\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; grpc_core::testing::TmpFile tmp_policy(policy); auto provider = CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 1); InitServer(provider); auto channel = BuildChannel(); ClientContext context1; grpc::testing::EchoResponse resp1; grpc::Status status = SendRpc(channel, &context1, &resp1); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp1.message(), kMessage); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":true,\"matched_rule\":\"allow_echo\"," "\"policy_name\":\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); audit_logs_.clear(); gpr_event on_first_reload_done; gpr_event_init(&on_first_reload_done); std::function callback1 = [&on_first_reload_done](bool contents_changed, absl::Status status) { if (contents_changed) { EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); EXPECT_EQ(status.message(), "\"name\" field is not present."); gpr_event_set(&on_first_reload_done, reinterpret_cast(1)); } }; dynamic_cast( provider->c_provider()) ->SetCallbackForTesting(std::move(callback1)); // Replaces existing policy with an invalid authorization policy. policy = "{}"; tmp_policy.RewriteFile(policy); // Wait for the provider's refresh thread to read the updated files. ASSERT_EQ(gpr_event_wait(&on_first_reload_done, gpr_inf_future(GPR_CLOCK_MONOTONIC)), reinterpret_cast(1)); ClientContext context2; grpc::testing::EchoResponse resp2; status = SendRpc(channel, &context2, &resp2); EXPECT_TRUE(status.ok()); EXPECT_EQ(resp2.message(), kMessage); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":true,\"matched_rule\":\"allow_echo\"," "\"policy_name\":\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); audit_logs_.clear(); gpr_event on_second_reload_done; gpr_event_init(&on_second_reload_done); std::function callback2 = [&on_second_reload_done](bool contents_changed, absl::Status status) { if (contents_changed) { EXPECT_TRUE(status.ok()); gpr_event_set(&on_second_reload_done, reinterpret_cast(1)); } }; dynamic_cast( provider->c_provider()) ->SetCallbackForTesting(std::move(callback2)); // Replace the existing invalid policy with a valid authorization // policy. policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" " \"name\": \"allow_foo\"," " \"request\": {" " \"paths\": [" " \"*/foo\"" " ]" " }" " }" " ]," " \"deny_rules\": [" " {" " \"name\": \"deny_echo\"," " \"request\": {" " \"paths\": [" " \"*/Echo\"" " ]" " }" " }" " ]," " \"audit_logging_options\": {" " \"audit_condition\": \"ON_DENY\"," " \"audit_loggers\": [" " {" " \"name\": \"test_logger\"" " }" " ]" " }" "}"; tmp_policy.RewriteFile(policy); // Wait for the provider's refresh thread to read the updated files. ASSERT_EQ(gpr_event_wait(&on_second_reload_done, gpr_inf_future(GPR_CLOCK_MONOTONIC)), reinterpret_cast(1)); ClientContext context3; grpc::testing::EchoResponse resp3; status = SendRpc(channel, &context3, &resp3); EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); EXPECT_TRUE(resp3.message().empty()); EXPECT_THAT( audit_logs_, ::testing::ElementsAre( "{\"authorized\":false,\"matched_rule\":\"deny_echo\"," "\"policy_name\":\"authz\",\"principal\":\"spiffe://foo.com/bar/" "baz\",\"rpc_method\":\"/grpc.testing.EchoTestService/Echo\"}")); dynamic_cast( provider->c_provider()) ->SetCallbackForTesting(nullptr); } } // namespace } // namespace testing } // namespace grpc int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); grpc::testing::TestEnvironment env(&argc, argv); const auto result = RUN_ALL_TESTS(); return result; }