diff --git a/BUILD b/BUILD index a09c2d9c081..fe494da9a12 100644 --- a/BUILD +++ b/BUILD @@ -3082,6 +3082,7 @@ grpc_cc_library( language = "c++", deps = [ "gpr_base", + "grpc_base", "grpc_matchers", "grpc_rbac_engine", "grpc_secure", diff --git a/grpc.def b/grpc.def index c45009c63f9..8c5d3d17407 100644 --- a/grpc.def +++ b/grpc.def @@ -162,6 +162,7 @@ EXPORTS grpc_xds_credentials_create grpc_xds_server_credentials_create grpc_authorization_policy_provider_static_data_create + grpc_authorization_policy_provider_file_watcher_create grpc_authorization_policy_provider_release grpc_raw_byte_buffer_create grpc_raw_compressed_byte_buffer_create diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h index 9edd70f39d7..2902100f1cb 100644 --- a/include/grpc/grpc_security.h +++ b/include/grpc/grpc_security.h @@ -1109,6 +1109,24 @@ grpc_authorization_policy_provider_static_data_create( const char* authz_policy, grpc_status_code* code, const char** error_details); +/** + * EXPERIMENTAL - Subject to change. + * Creates a grpc_authorization_policy_provider by watching for SDK + * authorization policy changes in filesystem. + * - authz_policy is the file path of SDK authorization policy. + * - refresh_interval_sec is the amount of time the internal thread would wait + * before checking for file updates. + * - code is the error status code on failure. On success, it equals + * GRPC_STATUS_OK. + * - error_details contains details about the error if any. If the + * initialization is successful, it will be null. Caller must use gpr_free to + * destroy this string. + */ +GRPCAPI grpc_authorization_policy_provider* +grpc_authorization_policy_provider_file_watcher_create( + const char* authz_policy_path, unsigned int refresh_interval_sec, + grpc_status_code* code, const char** error_details); + /** * EXPERIMENTAL - Subject to change. * Releases grpc_authorization_policy_provider object. The creator of diff --git a/include/grpcpp/security/authorization_policy_provider.h b/include/grpcpp/security/authorization_policy_provider.h index b3258b2fa11..883dc1a03c7 100644 --- a/include/grpcpp/security/authorization_policy_provider.h +++ b/include/grpcpp/security/authorization_policy_provider.h @@ -20,7 +20,7 @@ #include #include -// TODO(yihuazhang): remove the forward declaration here and include +// TODO(yihuazhang): remove the forward declarations here and include // directly once the insecure builds are cleaned up. typedef struct grpc_authorization_policy_provider grpc_authorization_policy_provider; @@ -61,6 +61,31 @@ class StaticDataAuthorizationPolicyProvider grpc_authorization_policy_provider* c_provider_ = nullptr; }; +// Implementation obtains authorization policy by watching for changes in +// filesystem. +class FileWatcherAuthorizationPolicyProvider + : public AuthorizationPolicyProviderInterface { + public: + static std::shared_ptr Create( + const std::string& authz_policy_path, unsigned int refresh_interval_sec, + grpc::Status* status); + + // Use factory method "Create" to create an instance of + // FileWatcherAuthorizationPolicyProvider. + explicit FileWatcherAuthorizationPolicyProvider( + grpc_authorization_policy_provider* provider) + : c_provider_(provider) {} + + ~FileWatcherAuthorizationPolicyProvider() override; + + grpc_authorization_policy_provider* c_provider() override { + return c_provider_; + } + + private: + grpc_authorization_policy_provider* c_provider_ = nullptr; +}; + } // namespace experimental } // namespace grpc diff --git a/src/core/lib/security/authorization/authorization_policy_provider.h b/src/core/lib/security/authorization/authorization_policy_provider.h index 0268a2c5960..7db900676bb 100644 --- a/src/core/lib/security/authorization/authorization_policy_provider.h +++ b/src/core/lib/security/authorization/authorization_policy_provider.h @@ -27,7 +27,7 @@ struct grpc_authorization_policy_provider grpc_core::RefCountedPtr allow_engine; grpc_core::RefCountedPtr deny_engine; }; - virtual AuthorizationEngines engines() const = 0; + virtual AuthorizationEngines engines() = 0; }; #endif // GRPC_CORE_LIB_SECURITY_AUTHORIZATION_AUTHORIZATION_POLICY_PROVIDER_H diff --git a/src/core/lib/security/authorization/grpc_authorization_engine.h b/src/core/lib/security/authorization/grpc_authorization_engine.h index c482f79f92a..773dbd269c6 100644 --- a/src/core/lib/security/authorization/grpc_authorization_engine.h +++ b/src/core/lib/security/authorization/grpc_authorization_engine.h @@ -38,6 +38,9 @@ class GrpcAuthorizationEngine : public AuthorizationEngine { Rbac::Action action() { return action_; } + // Required only for testing purpose. + size_t num_policies() { return policies_.size(); } + // Evaluates incoming request against RBAC policy and makes a decision to // whether allow/deny this request. Decision Evaluate(const EvaluateArgs& args) const override; diff --git a/src/core/lib/security/authorization/grpc_authorization_policy_provider.cc b/src/core/lib/security/authorization/grpc_authorization_policy_provider.cc index 7a0c77e9f53..c7236aac409 100644 --- a/src/core/lib/security/authorization/grpc_authorization_policy_provider.cc +++ b/src/core/lib/security/authorization/grpc_authorization_policy_provider.cc @@ -19,10 +19,14 @@ #include #include +#include "src/core/lib/iomgr/load_file.h" #include "src/core/lib/security/authorization/grpc_authorization_engine.h" +#include "src/core/lib/slice/slice_internal.h" namespace grpc_core { +extern TraceFlag grpc_sdk_authz_trace; + absl::StatusOr> StaticDataAuthorizationPolicyProvider::Create(absl::string_view authz_policy) { auto policies_or = GenerateRbacPolicies(authz_policy); @@ -40,6 +44,113 @@ StaticDataAuthorizationPolicyProvider::StaticDataAuthorizationPolicyProvider( deny_engine_(MakeRefCounted( std::move(policies.deny_policy))) {} +namespace { + +absl::StatusOr ReadPolicyFromFile(absl::string_view policy_path) { + grpc_slice policy_slice = grpc_empty_slice(); + grpc_error_handle error = + grpc_load_file(std::string(policy_path).c_str(), 0, &policy_slice); + if (error != GRPC_ERROR_NONE) { + absl::Status status = + absl::InvalidArgumentError(grpc_error_std_string(error)); + GRPC_ERROR_UNREF(error); + return status; + } + std::string policy_contents(StringViewFromSlice(policy_slice)); + grpc_slice_unref_internal(policy_slice); + return policy_contents; +} + +gpr_timespec TimeoutSecondsToDeadline(int64_t seconds) { + return gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(seconds, GPR_TIMESPAN)); +} + +} // namespace + +absl::StatusOr> +FileWatcherAuthorizationPolicyProvider::Create( + absl::string_view authz_policy_path, unsigned int refresh_interval_sec) { + GPR_ASSERT(!authz_policy_path.empty()); + GPR_ASSERT(refresh_interval_sec > 0); + absl::Status status; + auto provider = MakeRefCounted( + authz_policy_path, refresh_interval_sec, &status); + if (!status.ok()) return status; + return provider; +} + +FileWatcherAuthorizationPolicyProvider::FileWatcherAuthorizationPolicyProvider( + absl::string_view authz_policy_path, unsigned int refresh_interval_sec, + absl::Status* status) + : authz_policy_path_(std::string(authz_policy_path)), + refresh_interval_sec_(refresh_interval_sec) { + gpr_event_init(&shutdown_event_); + // Initial read is done synchronously. + *status = ForceUpdate(); + if (!status->ok()) { + return; + } + auto thread_lambda = [](void* arg) { + WeakRefCountedPtr provider( + static_cast(arg)); + GPR_ASSERT(provider != nullptr); + while (true) { + void* value = gpr_event_wait( + &provider->shutdown_event_, + TimeoutSecondsToDeadline(provider->refresh_interval_sec_)); + if (value != nullptr) { + return; + } + absl::Status status = provider->ForceUpdate(); + if (GRPC_TRACE_FLAG_ENABLED(grpc_sdk_authz_trace) && !status.ok()) { + gpr_log(GPR_ERROR, + "authorization policy reload status. code=%d error_details=%s", + status.code(), std::string(status.message()).c_str()); + } + } + }; + refresh_thread_ = absl::make_unique( + "FileWatcherAuthorizationPolicyProvider_refreshing_thread", thread_lambda, + WeakRef().release()); + refresh_thread_->Start(); +} + +absl::Status FileWatcherAuthorizationPolicyProvider::ForceUpdate() { + absl::StatusOr file_contents = + ReadPolicyFromFile(authz_policy_path_); + if (!file_contents.ok()) { + return file_contents.status(); + } + if (file_contents_ == *file_contents) { + return absl::OkStatus(); + } + file_contents_ = std::move(*file_contents); + auto rbac_policies_or = GenerateRbacPolicies(file_contents_); + if (!rbac_policies_or.ok()) { + return rbac_policies_or.status(); + } + grpc_core::MutexLock lock(&mu_); + allow_engine_ = MakeRefCounted( + std::move(rbac_policies_or->allow_policy)); + deny_engine_ = MakeRefCounted( + std::move(rbac_policies_or->deny_policy)); + if (GRPC_TRACE_FLAG_ENABLED(grpc_sdk_authz_trace)) { + gpr_log(GPR_INFO, + "authorization policy reload status: successfully loaded new " + "policy\n%s", + file_contents_.c_str()); + } + return absl::OkStatus(); +} + +void FileWatcherAuthorizationPolicyProvider::Orphan() { + gpr_event_set(&shutdown_event_, reinterpret_cast(1)); + if (refresh_thread_ != nullptr) { + refresh_thread_->Join(); + } +} + } // namespace grpc_core // Wrapper APIs declared in grpc_security.h @@ -57,8 +168,22 @@ grpc_authorization_policy_provider_static_data_create( gpr_strdup(std::string(provider_or.status().message()).c_str()); return nullptr; } - *code = GRPC_STATUS_OK; - *error_details = nullptr; + return provider_or->release(); +} + +grpc_authorization_policy_provider* +grpc_authorization_policy_provider_file_watcher_create( + const char* authz_policy_path, unsigned int refresh_interval_sec, + grpc_status_code* code, const char** error_details) { + GPR_ASSERT(authz_policy_path != nullptr); + auto provider_or = grpc_core::FileWatcherAuthorizationPolicyProvider::Create( + authz_policy_path, refresh_interval_sec); + if (!provider_or.ok()) { + *code = static_cast(provider_or.status().code()); + *error_details = + gpr_strdup(std::string(provider_or.status().message()).c_str()); + return nullptr; + } return provider_or->release(); } diff --git a/src/core/lib/security/authorization/grpc_authorization_policy_provider.h b/src/core/lib/security/authorization/grpc_authorization_policy_provider.h index 4e80ac60b66..44b74a37895 100644 --- a/src/core/lib/security/authorization/grpc_authorization_policy_provider.h +++ b/src/core/lib/security/authorization/grpc_authorization_policy_provider.h @@ -21,6 +21,8 @@ #include "absl/status/statusor.h" +#include "src/core/lib/gprpp/sync.h" +#include "src/core/lib/gprpp/thd.h" #include "src/core/lib/security/authorization/authorization_policy_provider.h" #include "src/core/lib/security/authorization/rbac_translator.h" @@ -36,9 +38,11 @@ class StaticDataAuthorizationPolicyProvider static absl::StatusOr> Create(absl::string_view authz_policy); + // Use factory method "Create" to create an instance of + // StaticDataAuthorizationPolicyProvider. explicit StaticDataAuthorizationPolicyProvider(RbacPolicies policies); - AuthorizationEngines engines() const override { + AuthorizationEngines engines() override { return {allow_engine_, deny_engine_}; } @@ -49,8 +53,50 @@ class StaticDataAuthorizationPolicyProvider RefCountedPtr deny_engine_; }; -// TODO(ashithasantosh): Add implementation for file watcher authorization -// policy provider. +// Provider class will get SDK Authorization policy from provided file path. +// This policy will be translated to Envoy RBAC policies and used to initialize +// allow and deny AuthorizationEngine objects. This provider will periodically +// load file contents in specified path, and upon modification update the engine +// instances with new policy configuration. During reload if the file contents +// are invalid or there are I/O errors, we will skip that particular update and +// log error status. The authorization decisions will be made using the latest +// valid policy. +class FileWatcherAuthorizationPolicyProvider + : public grpc_authorization_policy_provider { + public: + static absl::StatusOr> + Create(absl::string_view authz_policy_path, + unsigned int refresh_interval_sec); + + // Use factory method "Create" to create an instance of + // FileWatcherAuthorizationPolicyProvider. + FileWatcherAuthorizationPolicyProvider(absl::string_view authz_policy_path, + unsigned int refresh_interval_sec, + absl::Status* status); + + void Orphan() override; + + AuthorizationEngines engines() override { + grpc_core::MutexLock lock(&mu_); + return {allow_engine_, deny_engine_}; + } + + private: + // Force an update from the file system regardless of the interval. + absl::Status ForceUpdate(); + + std::string authz_policy_path_; + std::string file_contents_; + unsigned int refresh_interval_sec_; + + std::unique_ptr refresh_thread_; + gpr_event shutdown_event_; + + grpc_core::Mutex mu_; + // Engines created using authz_policy_. + RefCountedPtr allow_engine_ ABSL_GUARDED_BY(mu_); + RefCountedPtr deny_engine_ ABSL_GUARDED_BY(mu_); +}; } // namespace grpc_core diff --git a/src/cpp/server/authorization_policy_provider.cc b/src/cpp/server/authorization_policy_provider.cc index e5b51a59446..8dab33ddc3e 100644 --- a/src/cpp/server/authorization_policy_provider.cc +++ b/src/cpp/server/authorization_policy_provider.cc @@ -14,6 +14,7 @@ #include #include +#include #include namespace grpc { @@ -22,7 +23,7 @@ namespace experimental { std::shared_ptr StaticDataAuthorizationPolicyProvider::Create(const std::string& authz_policy, grpc::Status* status) { - grpc_status_code code; + grpc_status_code code = GRPC_STATUS_OK; const char* error_details; grpc_authorization_policy_provider* provider = grpc_authorization_policy_provider_static_data_create( @@ -41,5 +42,28 @@ StaticDataAuthorizationPolicyProvider:: grpc_authorization_policy_provider_release(c_provider_); } +std::shared_ptr +FileWatcherAuthorizationPolicyProvider::Create( + const std::string& authz_policy_path, unsigned int refresh_interval_sec, + grpc::Status* status) { + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_file_watcher_create( + authz_policy_path.c_str(), refresh_interval_sec, &code, + &error_details); + if (code != GRPC_STATUS_OK) { + *status = grpc::Status(static_cast(code), error_details); + gpr_free(const_cast(error_details)); + return nullptr; + } + return std::make_shared(provider); +} + +FileWatcherAuthorizationPolicyProvider:: + ~FileWatcherAuthorizationPolicyProvider() { + grpc_authorization_policy_provider_release(c_provider_); +} + } // namespace experimental } // namespace grpc diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.c b/src/ruby/ext/grpc/rb_grpc_imports.generated.c index d5683ecb4ca..874a5e9e9f7 100644 --- a/src/ruby/ext/grpc/rb_grpc_imports.generated.c +++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.c @@ -185,6 +185,7 @@ grpc_tls_server_authorization_check_config_release_type grpc_tls_server_authoriz grpc_xds_credentials_create_type grpc_xds_credentials_create_import; grpc_xds_server_credentials_create_type grpc_xds_server_credentials_create_import; grpc_authorization_policy_provider_static_data_create_type grpc_authorization_policy_provider_static_data_create_import; +grpc_authorization_policy_provider_file_watcher_create_type grpc_authorization_policy_provider_file_watcher_create_import; grpc_authorization_policy_provider_release_type grpc_authorization_policy_provider_release_import; grpc_raw_byte_buffer_create_type grpc_raw_byte_buffer_create_import; grpc_raw_compressed_byte_buffer_create_type grpc_raw_compressed_byte_buffer_create_import; @@ -474,6 +475,7 @@ void grpc_rb_load_imports(HMODULE library) { grpc_xds_credentials_create_import = (grpc_xds_credentials_create_type) GetProcAddress(library, "grpc_xds_credentials_create"); grpc_xds_server_credentials_create_import = (grpc_xds_server_credentials_create_type) GetProcAddress(library, "grpc_xds_server_credentials_create"); grpc_authorization_policy_provider_static_data_create_import = (grpc_authorization_policy_provider_static_data_create_type) GetProcAddress(library, "grpc_authorization_policy_provider_static_data_create"); + grpc_authorization_policy_provider_file_watcher_create_import = (grpc_authorization_policy_provider_file_watcher_create_type) GetProcAddress(library, "grpc_authorization_policy_provider_file_watcher_create"); grpc_authorization_policy_provider_release_import = (grpc_authorization_policy_provider_release_type) GetProcAddress(library, "grpc_authorization_policy_provider_release"); grpc_raw_byte_buffer_create_import = (grpc_raw_byte_buffer_create_type) GetProcAddress(library, "grpc_raw_byte_buffer_create"); grpc_raw_compressed_byte_buffer_create_import = (grpc_raw_compressed_byte_buffer_create_type) GetProcAddress(library, "grpc_raw_compressed_byte_buffer_create"); diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h index 7e5c21ada7e..4667bce7a43 100644 --- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h +++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h @@ -530,6 +530,9 @@ extern grpc_xds_server_credentials_create_type grpc_xds_server_credentials_creat typedef grpc_authorization_policy_provider*(*grpc_authorization_policy_provider_static_data_create_type)(const char* authz_policy, grpc_status_code* code, const char** error_details); extern grpc_authorization_policy_provider_static_data_create_type grpc_authorization_policy_provider_static_data_create_import; #define grpc_authorization_policy_provider_static_data_create grpc_authorization_policy_provider_static_data_create_import +typedef grpc_authorization_policy_provider*(*grpc_authorization_policy_provider_file_watcher_create_type)(const char* authz_policy_path, unsigned int refresh_interval_sec, grpc_status_code* code, const char** error_details); +extern grpc_authorization_policy_provider_file_watcher_create_type grpc_authorization_policy_provider_file_watcher_create_import; +#define grpc_authorization_policy_provider_file_watcher_create grpc_authorization_policy_provider_file_watcher_create_import typedef void(*grpc_authorization_policy_provider_release_type)(grpc_authorization_policy_provider* provider); extern grpc_authorization_policy_provider_release_type grpc_authorization_policy_provider_release_import; #define grpc_authorization_policy_provider_release grpc_authorization_policy_provider_release_import diff --git a/test/core/end2end/tests/sdk_authz.cc b/test/core/end2end/tests/sdk_authz.cc index 6ab5e0b3192..19e228bd4d5 100644 --- a/test/core/end2end/tests/sdk_authz.cc +++ b/test/core/end2end/tests/sdk_authz.cc @@ -27,6 +27,7 @@ #include "src/core/lib/security/credentials/credentials.h" #include "test/core/end2end/cq_verifier.h" #include "test/core/end2end/end2end_tests.h" +#include "test/core/util/tls_utils.h" static void* tag(intptr_t t) { return reinterpret_cast(t); } @@ -84,7 +85,7 @@ static void end_test(grpc_end2end_test_fixture* f) { grpc_completion_queue_destroy(f->shutdown_cq); } -static void test_allow_authorized_request(grpc_end2end_test_config config) { +static void test_allow_authorized_request(grpc_end2end_test_fixture f) { grpc_call* c; grpc_call* s; grpc_op ops[6]; @@ -99,36 +100,6 @@ static void test_allow_authorized_request(grpc_end2end_test_config config) { grpc_slice details = grpc_empty_slice(); int was_cancelled = 2; - const char* authz_policy = - "{" - " \"name\": \"authz\"," - " \"allow_rules\": [" - " {" - " \"name\": \"allow_foo\"," - " \"request\": {" - " \"paths\": [" - " \"*/foo\"" - " ]" - " }" - " }" - " ]" - "}"; - grpc_status_code code; - const char* error_details; - grpc_authorization_policy_provider* provider = - grpc_authorization_policy_provider_static_data_create(authz_policy, &code, - &error_details); - GPR_ASSERT(GRPC_STATUS_OK == code); - grpc_arg args[] = { - grpc_channel_arg_pointer_create( - const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, - grpc_authorization_policy_provider_arg_vtable()), - }; - grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; - - grpc_end2end_test_fixture f = begin_test( - config, "test_allow_authorized_request", nullptr, &server_args); - grpc_authorization_policy_provider_release(provider); cq_verifier* cqv = cq_verifier_create(f.cq); gpr_timespec deadline = five_seconds_from_now(); @@ -217,12 +188,9 @@ static void test_allow_authorized_request(grpc_end2end_test_config config) { grpc_call_unref(c); grpc_call_unref(s); cq_verifier_destroy(cqv); - - end_test(&f); - config.tear_down_data(&f); } -static void test_deny_unauthorized_request(grpc_end2end_test_config config) { +static void test_deny_unauthorized_request(grpc_end2end_test_fixture f) { grpc_call* c; grpc_op ops[6]; grpc_op* op; @@ -233,51 +201,11 @@ static void test_deny_unauthorized_request(grpc_end2end_test_config config) { grpc_call_error error; grpc_slice details = grpc_empty_slice(); - const char* authz_policy = - "{" - " \"name\": \"authz\"," - " \"allow_rules\": [" - " {" - " \"name\": \"allow_foo\"," - " \"request\": {" - " \"paths\": [" - " \"*/foo\"" - " ]" - " }" - " }" - " ]," - " \"deny_rules\": [" - " {" - " \"name\": \"deny_bar\"," - " \"request\": {" - " \"paths\": [" - " \"*/bar\"" - " ]" - " }" - " }" - " ]" - "}"; - grpc_status_code code; - const char* error_details; - grpc_authorization_policy_provider* provider = - grpc_authorization_policy_provider_static_data_create(authz_policy, &code, - &error_details); - GPR_ASSERT(GRPC_STATUS_OK == code); - grpc_arg args[] = { - grpc_channel_arg_pointer_create( - const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, - grpc_authorization_policy_provider_arg_vtable()), - }; - grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; - - grpc_end2end_test_fixture f = begin_test( - config, "test_deny_unauthorized_request", nullptr, &server_args); - grpc_authorization_policy_provider_release(provider); cq_verifier* cqv = cq_verifier_create(f.cq); gpr_timespec deadline = five_seconds_from_now(); c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq, - grpc_slice_from_static_string("/bar"), nullptr, + grpc_slice_from_static_string("/foo"), nullptr, deadline, nullptr); GPR_ASSERT(c); @@ -325,29 +253,65 @@ static void test_deny_unauthorized_request(grpc_end2end_test_config config) { grpc_call_unref(c); cq_verifier_destroy(cqv); +} + +static void test_static_init_allow_authorized_request( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_static_data_create(authz_policy, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; + + grpc_end2end_test_fixture f = + begin_test(config, "test_static_init_allow_authorized_request", nullptr, + &server_args); + grpc_authorization_policy_provider_release(provider); + test_allow_authorized_request(f); end_test(&f); config.tear_down_data(&f); } -static void test_deny_request_no_match_in_policy( +static void test_static_init_deny_unauthorized_request( grpc_end2end_test_config config) { - grpc_call* c; - grpc_op ops[6]; - grpc_op* op; - grpc_metadata_array initial_metadata_recv; - grpc_metadata_array trailing_metadata_recv; - grpc_status_code status; - const char* error_string = nullptr; - grpc_call_error error; - grpc_slice details = grpc_empty_slice(); - const char* authz_policy = "{" " \"name\": \"authz\"," " \"allow_rules\": [" " {" - " \"name\": \"allow_foo\"," + " \"name\": \"allow_bar\"," + " \"request\": {" + " \"paths\": [" + " \"*/bar\"" + " ]" + " }" + " }" + " ]," + " \"deny_rules\": [" + " {" + " \"name\": \"deny_foo\"," " \"request\": {" " \"paths\": [" " \"*/foo\"" @@ -356,7 +320,46 @@ static void test_deny_request_no_match_in_policy( " }" " ]" "}"; - grpc_status_code code; + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_static_data_create(authz_policy, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; + + grpc_end2end_test_fixture f = + begin_test(config, "test_static_init_deny_unauthorized_request", nullptr, + &server_args); + grpc_authorization_policy_provider_release(provider); + test_deny_unauthorized_request(f); + + end_test(&f); + config.tear_down_data(&f); +} + +static void test_static_init_deny_request_no_match_in_policy( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_bar\"," + " \"request\": {" + " \"paths\": [" + " \"*/bar\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_status_code code = GRPC_STATUS_OK; const char* error_details; grpc_authorization_policy_provider* provider = grpc_authorization_policy_provider_static_data_create(authz_policy, &code, @@ -369,70 +372,351 @@ static void test_deny_request_no_match_in_policy( }; grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; + grpc_end2end_test_fixture f = + begin_test(config, "test_static_init_deny_request_no_match_in_policy", + nullptr, &server_args); + grpc_authorization_policy_provider_release(provider); + test_deny_unauthorized_request(f); + + end_test(&f); + config.tear_down_data(&f); +} + +static void test_file_watcher_init_allow_authorized_request( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(authz_policy); + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_file_watcher_create( + tmp_policy.name().c_str(), /*refresh_interval_sec=*/1, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; + + grpc_end2end_test_fixture f = + begin_test(config, "test_file_watcher_init_allow_authorized_request", + nullptr, &server_args); + grpc_authorization_policy_provider_release(provider); + test_allow_authorized_request(f); + + end_test(&f); + config.tear_down_data(&f); +} + +static void test_file_watcher_init_deny_unauthorized_request( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_bar\"," + " \"request\": {" + " \"paths\": [" + " \"*/bar\"" + " ]" + " }" + " }" + " ]," + " \"deny_rules\": [" + " {" + " \"name\": \"deny_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(authz_policy); + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_file_watcher_create( + tmp_policy.name().c_str(), /*refresh_interval_sec=*/1, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; + + grpc_end2end_test_fixture f = + begin_test(config, "test_file_watcher_init_deny_unauthorized_request", + nullptr, &server_args); + grpc_authorization_policy_provider_release(provider); + test_deny_unauthorized_request(f); + + end_test(&f); + config.tear_down_data(&f); +} + +static void test_file_watcher_init_deny_request_no_match_in_policy( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_bar\"," + " \"request\": {" + " \"paths\": [" + " \"*/bar\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(authz_policy); + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_file_watcher_create( + tmp_policy.name().c_str(), /*refresh_interval_sec=*/1, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; + grpc_end2end_test_fixture f = begin_test( - config, "test_deny_request_no_match_in_policy", nullptr, &server_args); + config, "test_file_watcher_init_deny_request_no_match_in_policy", nullptr, + &server_args); grpc_authorization_policy_provider_release(provider); - cq_verifier* cqv = cq_verifier_create(f.cq); + test_deny_unauthorized_request(f); - gpr_timespec deadline = five_seconds_from_now(); - c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq, - grpc_slice_from_static_string("/bar"), nullptr, - deadline, nullptr); - GPR_ASSERT(c); + end_test(&f); + config.tear_down_data(&f); +} - grpc_metadata_array_init(&initial_metadata_recv); - grpc_metadata_array_init(&trailing_metadata_recv); +static void test_file_watcher_valid_policy_reload( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(authz_policy); + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_file_watcher_create( + tmp_policy.name().c_str(), /*refresh_interval_sec=*/1, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; - memset(ops, 0, sizeof(ops)); - op = ops; - op->op = GRPC_OP_SEND_INITIAL_METADATA; - op->data.send_initial_metadata.count = 0; - op->flags = 0; - op->reserved = nullptr; - op++; - op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; - op->flags = 0; - op->reserved = nullptr; - op++; - op->op = GRPC_OP_RECV_INITIAL_METADATA; - op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv; - op->flags = 0; - op->reserved = nullptr; - op++; - op->op = GRPC_OP_RECV_STATUS_ON_CLIENT; - op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv; - op->data.recv_status_on_client.status = &status; - op->data.recv_status_on_client.status_details = &details; - op->data.recv_status_on_client.error_string = &error_string; - op->flags = 0; - op->reserved = nullptr; - op++; - error = grpc_call_start_batch(c, ops, static_cast(op - ops), tag(1), - nullptr); - GPR_ASSERT(GRPC_CALL_OK == error); - CQ_EXPECT_COMPLETION(cqv, tag(1), 1); - cq_verify(cqv); + grpc_end2end_test_fixture f = begin_test( + config, "test_file_watcher_valid_policy_reload", nullptr, &server_args); + grpc_authorization_policy_provider_release(provider); + test_allow_authorized_request(f); + // Replace existing policy in file with a different authorization policy. + authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_bar\"," + " \"request\": {" + " \"paths\": [" + " \"*/bar\"" + " ]" + " }" + " }" + " ]," + " \"deny_rules\": [" + " {" + " \"name\": \"deny_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + tmp_policy.RewriteFile(authz_policy); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + test_deny_unauthorized_request(f); - GPR_ASSERT(GRPC_STATUS_PERMISSION_DENIED == status); - GPR_ASSERT(0 == - grpc_slice_str_cmp(details, "Unauthorized RPC request rejected.")); + end_test(&f); + config.tear_down_data(&f); +} - grpc_slice_unref(details); - gpr_free(const_cast(error_string)); - grpc_metadata_array_destroy(&initial_metadata_recv); - grpc_metadata_array_destroy(&trailing_metadata_recv); +static void test_file_watcher_invalid_policy_skip_reload( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(authz_policy); + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_file_watcher_create( + tmp_policy.name().c_str(), /*refresh_interval_sec=*/1, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; - grpc_call_unref(c); - cq_verifier_destroy(cqv); + grpc_end2end_test_fixture f = + begin_test(config, "test_file_watcher_invalid_policy_skip_reload", + nullptr, &server_args); + grpc_authorization_policy_provider_release(provider); + test_allow_authorized_request(f); + // Replace exisiting policy in file with an invalid policy. + authz_policy = "{}"; + tmp_policy.RewriteFile(authz_policy); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + test_allow_authorized_request(f); + + end_test(&f); + config.tear_down_data(&f); +} + +static void test_file_watcher_recovers_from_failure( + grpc_end2end_test_config config) { + const char* authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(authz_policy); + grpc_status_code code = GRPC_STATUS_OK; + const char* error_details; + grpc_authorization_policy_provider* provider = + grpc_authorization_policy_provider_file_watcher_create( + tmp_policy.name().c_str(), /*refresh_interval_sec=*/1, &code, + &error_details); + GPR_ASSERT(GRPC_STATUS_OK == code); + grpc_arg args[] = { + grpc_channel_arg_pointer_create( + const_cast(GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER), provider, + grpc_authorization_policy_provider_arg_vtable()), + }; + grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args}; + + grpc_end2end_test_fixture f = begin_test( + config, "test_file_watcher_valid_policy_reload", nullptr, &server_args); + grpc_authorization_policy_provider_release(provider); + test_allow_authorized_request(f); + // Replace exisiting policy in file with an invalid policy. + authz_policy = "{}"; + tmp_policy.RewriteFile(authz_policy); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + test_allow_authorized_request(f); + // Recover from reload errors, by replacing invalid policy in file with a + // valid policy. + authz_policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_bar\"," + " \"request\": {" + " \"paths\": [" + " \"*/bar\"" + " ]" + " }" + " }" + " ]," + " \"deny_rules\": [" + " {" + " \"name\": \"deny_foo\"," + " \"request\": {" + " \"paths\": [" + " \"*/foo\"" + " ]" + " }" + " }" + " ]" + "}"; + tmp_policy.RewriteFile(authz_policy); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + test_deny_unauthorized_request(f); end_test(&f); config.tear_down_data(&f); } void sdk_authz(grpc_end2end_test_config config) { - test_allow_authorized_request(config); - test_deny_unauthorized_request(config); - test_deny_request_no_match_in_policy(config); + test_static_init_allow_authorized_request(config); + test_static_init_deny_unauthorized_request(config); + test_static_init_deny_request_no_match_in_policy(config); + test_file_watcher_init_allow_authorized_request(config); + test_file_watcher_init_deny_unauthorized_request(config); + test_file_watcher_init_deny_request_no_match_in_policy(config); + test_file_watcher_valid_policy_reload(config); + test_file_watcher_invalid_policy_skip_reload(config); + test_file_watcher_recovers_from_failure(config); } void sdk_authz_pre_init(void) {} diff --git a/test/core/security/BUILD b/test/core/security/BUILD index 68907f29f58..d5db0d73561 100644 --- a/test/core/security/BUILD +++ b/test/core/security/BUILD @@ -454,6 +454,11 @@ grpc_cc_test( grpc_cc_test( name = "grpc_authorization_policy_provider_test", srcs = ["grpc_authorization_policy_provider_test.cc"], + data = [ + "//test/core/security/authorization/test_policies:invalid_policy.json", + "//test/core/security/authorization/test_policies:valid_policy_1.json", + "//test/core/security/authorization/test_policies:valid_policy_2.json", + ], external_deps = ["gtest"], language = "C++", deps = [ diff --git a/test/core/security/authorization/test_policies/BUILD b/test/core/security/authorization/test_policies/BUILD new file mode 100644 index 00000000000..28a74357831 --- /dev/null +++ b/test/core/security/authorization/test_policies/BUILD @@ -0,0 +1,21 @@ +# 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. + +licenses(["notice"]) + +exports_files([ + "invalid_policy.json", + "valid_policy_1.json", + "valid_policy_2.json", +]) diff --git a/test/core/security/authorization/test_policies/invalid_policy.json b/test/core/security/authorization/test_policies/invalid_policy.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/test/core/security/authorization/test_policies/invalid_policy.json @@ -0,0 +1 @@ +{} diff --git a/test/core/security/authorization/test_policies/valid_policy_1.json b/test/core/security/authorization/test_policies/valid_policy_1.json new file mode 100644 index 00000000000..7f277df7803 --- /dev/null +++ b/test/core/security/authorization/test_policies/valid_policy_1.json @@ -0,0 +1,40 @@ +{ + "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" + ] + } + } + ] +} diff --git a/test/core/security/authorization/test_policies/valid_policy_2.json b/test/core/security/authorization/test_policies/valid_policy_2.json new file mode 100644 index 00000000000..4337ed42ea9 --- /dev/null +++ b/test/core/security/authorization/test_policies/valid_policy_2.json @@ -0,0 +1,26 @@ +{ + "name": "authz", + "allow_rules": + [ + { + "name": "allow_foo", + "request": + { + "paths": + [ + "*/foo" + ] + } + }, + { + "name": "allow_bar", + "request": + { + "paths": + [ + "*/bar" + ] + } + } + ] +} diff --git a/test/core/security/grpc_authorization_policy_provider_test.cc b/test/core/security/grpc_authorization_policy_provider_test.cc index f298a352239..3f1c87c8564 100644 --- a/test/core/security/grpc_authorization_policy_provider_test.cc +++ b/test/core/security/grpc_authorization_policy_provider_test.cc @@ -23,43 +23,197 @@ #include "src/core/lib/security/authorization/grpc_authorization_engine.h" #include "test/core/util/test_config.h" +#include "test/core/util/tls_utils.h" + +#define VALID_POLICY_PATH_1 \ + "test/core/security/authorization/test_policies/valid_policy_1.json" +#define VALID_POLICY_PATH_2 \ + "test/core/security/authorization/test_policies/valid_policy_2.json" +#define INVALID_POLICY_PATH \ + "test/core/security/authorization/test_policies/invalid_policy.json" namespace grpc_core { TEST(AuthorizationPolicyProviderTest, StaticDataInitializationSuccessful) { - const char* authz_policy = - "{" - " \"name\": \"authz\"," - " \"allow_rules\": [" - " {" - " \"name\": \"allow_policy\"" - " }" - " ]" - "}"; - auto provider = StaticDataAuthorizationPolicyProvider::Create(authz_policy); + auto provider = StaticDataAuthorizationPolicyProvider::Create( + testing::GetFileContents(VALID_POLICY_PATH_1)); ASSERT_TRUE(provider.ok()); auto engines = (*provider)->engines(); - ASSERT_NE(engines.allow_engine, nullptr); - EXPECT_EQ(dynamic_cast(engines.allow_engine.get()) - ->action(), - Rbac::Action::kAllow); - ASSERT_NE(engines.deny_engine, nullptr); - EXPECT_EQ(dynamic_cast(engines.deny_engine.get()) - ->action(), - Rbac::Action::kDeny); + auto* allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 1); + auto* deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 1); } TEST(AuthorizationPolicyProviderTest, StaticDataInitializationFailedInvalidPolicy) { - const char* authz_policy = "{}"; - auto provider = StaticDataAuthorizationPolicyProvider::Create(authz_policy); + auto provider = StaticDataAuthorizationPolicyProvider::Create( + testing::GetFileContents(INVALID_POLICY_PATH)); + EXPECT_EQ(provider.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(provider.status().message(), "\"name\" field is not present."); +} + +TEST(AuthorizationPolicyProviderTest, + FileWatcherInitializationSuccessValidPolicy) { + auto tmp_authz_policy = absl::make_unique( + testing::GetFileContents(VALID_POLICY_PATH_1)); + auto provider = FileWatcherAuthorizationPolicyProvider::Create( + tmp_authz_policy->name(), /*refresh_interval_sec=*/1); + ASSERT_TRUE(provider.ok()); + auto engines = (*provider)->engines(); + auto* allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 1); + auto* deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 1); +} + +TEST(AuthorizationPolicyProviderTest, + FileWatcherInitializationFailedInvalidPolicy) { + auto tmp_authz_policy = absl::make_unique( + testing::GetFileContents(INVALID_POLICY_PATH)); + auto provider = FileWatcherAuthorizationPolicyProvider::Create( + tmp_authz_policy->name(), /*refresh_interval_sec=*/1); EXPECT_EQ(provider.status().code(), absl::StatusCode::kInvalidArgument); EXPECT_EQ(provider.status().message(), "\"name\" field is not present."); } +TEST(AuthorizationPolicyProviderTest, FileWatcherSuccessValidPolicyRefresh) { + auto tmp_authz_policy = absl::make_unique( + testing::GetFileContents(VALID_POLICY_PATH_1)); + auto provider = FileWatcherAuthorizationPolicyProvider::Create( + tmp_authz_policy->name(), /*refresh_interval_sec=*/1); + ASSERT_TRUE(provider.ok()); + auto engines = (*provider)->engines(); + auto* allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 1); + auto* deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 1); + // Rewrite the file with a different valid authorization policy. + tmp_authz_policy->RewriteFile(testing::GetFileContents(VALID_POLICY_PATH_2)); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + engines = (*provider)->engines(); + allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 2); + deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 0); +} + +TEST(AuthorizationPolicyProviderTest, + FileWatcherInvalidPolicyRefreshSkipReload) { + auto tmp_authz_policy = absl::make_unique( + testing::GetFileContents(VALID_POLICY_PATH_1)); + auto provider = FileWatcherAuthorizationPolicyProvider::Create( + tmp_authz_policy->name(), /*refresh_interval_sec=*/1); + ASSERT_TRUE(provider.ok()); + auto engines = (*provider)->engines(); + auto* allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 1); + auto* deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 1); + // Skips the following policy update, and continues to use the valid policy. + tmp_authz_policy->RewriteFile(testing::GetFileContents(INVALID_POLICY_PATH)); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + engines = (*provider)->engines(); + allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 1); + deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 1); +} + +TEST(AuthorizationPolicyProviderTest, FileWatcherRecoversFromFailure) { + auto tmp_authz_policy = absl::make_unique( + testing::GetFileContents(VALID_POLICY_PATH_1)); + auto provider = FileWatcherAuthorizationPolicyProvider::Create( + tmp_authz_policy->name(), /*refresh_interval_sec=*/1); + ASSERT_TRUE(provider.ok()); + auto engines = (*provider)->engines(); + auto* allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 1); + auto* deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 1); + // Skips the following policy update, and continues to use the valid policy. + tmp_authz_policy->RewriteFile(testing::GetFileContents(INVALID_POLICY_PATH)); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + engines = (*provider)->engines(); + allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 1); + deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 1); + // Rewrite the file with a valid authorization policy. + tmp_authz_policy->RewriteFile(testing::GetFileContents(VALID_POLICY_PATH_2)); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + engines = (*provider)->engines(); + allow_engine = + dynamic_cast(engines.allow_engine.get()); + ASSERT_NE(allow_engine, nullptr); + EXPECT_EQ(allow_engine->action(), Rbac::Action::kAllow); + EXPECT_EQ(allow_engine->num_policies(), 2); + deny_engine = + dynamic_cast(engines.deny_engine.get()); + ASSERT_NE(deny_engine, nullptr); + EXPECT_EQ(deny_engine->action(), Rbac::Action::kDeny); + EXPECT_EQ(deny_engine->num_policies(), 0); +} + } // namespace grpc_core int main(int argc, char** argv) { + grpc::testing::TestEnvironment env(argc, argv); ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + grpc_init(); + int ret = RUN_ALL_TESTS(); + grpc_shutdown(); + return ret; } diff --git a/test/core/surface/public_headers_must_be_c89.c b/test/core/surface/public_headers_must_be_c89.c index 3e59b9e5c92..209264fa56c 100644 --- a/test/core/surface/public_headers_must_be_c89.c +++ b/test/core/surface/public_headers_must_be_c89.c @@ -229,6 +229,7 @@ int main(int argc, char **argv) { printf("%lx", (unsigned long) grpc_xds_credentials_create); printf("%lx", (unsigned long) grpc_xds_server_credentials_create); printf("%lx", (unsigned long) grpc_authorization_policy_provider_static_data_create); + printf("%lx", (unsigned long) grpc_authorization_policy_provider_file_watcher_create); printf("%lx", (unsigned long) grpc_authorization_policy_provider_release); printf("%lx", (unsigned long) grpc_raw_byte_buffer_create); printf("%lx", (unsigned long) grpc_raw_compressed_byte_buffer_create); diff --git a/test/cpp/end2end/sdk_authz_end2end_test.cc b/test/cpp/end2end/sdk_authz_end2end_test.cc index 853920a6655..3b6fb4a47e0 100644 --- a/test/cpp/end2end/sdk_authz_end2end_test.cc +++ b/test/cpp/end2end/sdk_authz_end2end_test.cc @@ -28,6 +28,7 @@ #include "src/proto/grpc/testing/echo.grpc.pb.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 { @@ -76,6 +77,17 @@ class SdkAuthzEnd2EndTest : public ::testing::Test { 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; return ::grpc::CreateCustomChannel(server_address_, channel_creds_, args); @@ -348,6 +360,397 @@ TEST_F( EXPECT_TRUE(resp.message().empty()); } +TEST_F(SdkAuthzEnd2EndTest, + 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(SdkAuthzEnd2EndTest, + 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(SdkAuthzEnd2EndTest, + 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(SdkAuthzEnd2EndTest, + 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(SdkAuthzEnd2EndTest, + 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(SdkAuthzEnd2EndTest, + 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(SdkAuthzEnd2EndTest, FileWatcherValidPolicyRefresh) { + std::string policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_echo\"," + " \"request\": {" + " \"paths\": [" + " \"*/Echo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(policy); + InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 1)); + 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); + // 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 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + 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()); +} + +TEST_F(SdkAuthzEnd2EndTest, FileWatcherInvalidPolicyRefreshSkipsReload) { + std::string policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_echo\"," + " \"request\": {" + " \"paths\": [" + " \"*/Echo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(policy); + InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 1)); + 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); + // Replaces existing policy with an invalid authorization policy. + policy = "{}"; + tmp_policy.RewriteFile(policy); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + ClientContext context2; + grpc::testing::EchoResponse resp2; + status = SendRpc(channel, &context2, &resp2); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(resp2.message(), kMessage); +} + +TEST_F(SdkAuthzEnd2EndTest, FileWatcherRecoversFromFailure) { + std::string policy = + "{" + " \"name\": \"authz\"," + " \"allow_rules\": [" + " {" + " \"name\": \"allow_echo\"," + " \"request\": {" + " \"paths\": [" + " \"*/Echo\"" + " ]" + " }" + " }" + " ]" + "}"; + grpc_core::testing::TmpFile tmp_policy(policy); + InitServer(CreateFileWatcherAuthzPolicyProvider(tmp_policy.name(), 1)); + 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); + // Replaces existing policy with an invalid authorization policy. + policy = "{}"; + tmp_policy.RewriteFile(policy); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + ClientContext context2; + grpc::testing::EchoResponse resp2; + status = SendRpc(channel, &context2, &resp2); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(resp2.message(), kMessage); + // 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\"" + " ]" + " }" + " }" + " ]" + "}"; + tmp_policy.RewriteFile(policy); + // Wait 2 seconds for the provider's refresh thread to read the updated files. + gpr_sleep_until(grpc_timeout_seconds_to_deadline(2)); + 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()); +} + } // namespace } // namespace testing } // namespace grpc diff --git a/test/cpp/server/BUILD b/test/cpp/server/BUILD index 505a0f2b451..82242686446 100644 --- a/test/cpp/server/BUILD +++ b/test/cpp/server/BUILD @@ -82,6 +82,11 @@ grpc_cc_test( grpc_cc_test( name = "authorization_policy_provider_test", srcs = ["authorization_policy_provider_test.cc"], + data = [ + "//test/core/security/authorization/test_policies:invalid_policy.json", + "//test/core/security/authorization/test_policies:valid_policy_1.json", + "//test/core/security/authorization/test_policies:valid_policy_2.json", + ], external_deps = [ "gtest", ], diff --git a/test/cpp/server/authorization_policy_provider_test.cc b/test/cpp/server/authorization_policy_provider_test.cc index 9b84af833e0..b64264610c4 100644 --- a/test/cpp/server/authorization_policy_provider_test.cc +++ b/test/cpp/server/authorization_policy_provider_test.cc @@ -17,22 +17,21 @@ #include #include "test/core/util/test_config.h" +#include "test/core/util/tls_utils.h" + +#define VALID_POLICY_PATH_1 \ + "test/core/security/authorization/test_policies/valid_policy_1.json" +#define VALID_POLICY_PATH_2 \ + "test/core/security/authorization/test_policies/valid_policy_2.json" +#define INVALID_POLICY_PATH \ + "test/core/security/authorization/test_policies/invalid_policy.json" namespace grpc { TEST(AuthorizationPolicyProviderTest, StaticDataCreateReturnsProvider) { - const char* authz_policy = - "{" - " \"name\": \"authz\"," - " \"allow_rules\": [" - " {" - " \"name\": \"allow_policy\"" - " }" - " ]" - "}"; grpc::Status status; auto provider = experimental::StaticDataAuthorizationPolicyProvider::Create( - authz_policy, &status); + grpc_core::testing::GetFileContents(VALID_POLICY_PATH_1), &status); ASSERT_NE(provider, nullptr); EXPECT_NE(provider->c_provider(), nullptr); EXPECT_TRUE(status.ok()); @@ -40,10 +39,32 @@ TEST(AuthorizationPolicyProviderTest, StaticDataCreateReturnsProvider) { } TEST(AuthorizationPolicyProviderTest, StaticDataCreateReturnsErrorStatus) { - const char* authz_policy = "{}"; grpc::Status status; auto provider = experimental::StaticDataAuthorizationPolicyProvider::Create( - authz_policy, &status); + grpc_core::testing::GetFileContents(INVALID_POLICY_PATH), &status); + ASSERT_EQ(provider, nullptr); + EXPECT_EQ(status.error_code(), grpc::StatusCode::INVALID_ARGUMENT); + EXPECT_EQ(status.error_message(), "\"name\" field is not present."); +} + +TEST(AuthorizationPolicyProviderTest, FileWatcherCreateReturnsProvider) { + auto tmp_authz_policy = absl::make_unique( + grpc_core::testing::GetFileContents(VALID_POLICY_PATH_1)); + grpc::Status status; + auto provider = experimental::FileWatcherAuthorizationPolicyProvider::Create( + tmp_authz_policy->name(), /*refresh_interval_sec=*/1, &status); + ASSERT_NE(provider, nullptr); + EXPECT_NE(provider->c_provider(), nullptr); + EXPECT_TRUE(status.ok()); + EXPECT_TRUE(status.error_message().empty()); +} + +TEST(AuthorizationPolicyProviderTest, FileWatcherCreateReturnsErrorStatus) { + auto tmp_authz_policy = absl::make_unique( + grpc_core::testing::GetFileContents(INVALID_POLICY_PATH)); + grpc::Status status; + auto provider = experimental::FileWatcherAuthorizationPolicyProvider::Create( + tmp_authz_policy->name(), /*refresh_interval_sec=*/1, &status); ASSERT_EQ(provider, nullptr); EXPECT_EQ(status.error_code(), grpc::StatusCode::INVALID_ARGUMENT); EXPECT_EQ(status.error_message(), "\"name\" field is not present.");