Revert "Revert "Revert "Revert "File watcher authorization policy provider implementation"" (#27605)" (#27644)" (#27645)

This reverts commit b8e01f73a0.
reviewable/pr25586/r10^2
Ashitha Santhosh 3 years ago committed by GitHub
parent 0f050e6f7a
commit a1db97be90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      BUILD
  2. 1
      grpc.def
  3. 18
      include/grpc/grpc_security.h
  4. 27
      include/grpcpp/security/authorization_policy_provider.h
  5. 2
      src/core/lib/security/authorization/authorization_policy_provider.h
  6. 3
      src/core/lib/security/authorization/grpc_authorization_engine.h
  7. 129
      src/core/lib/security/authorization/grpc_authorization_policy_provider.cc
  8. 52
      src/core/lib/security/authorization/grpc_authorization_policy_provider.h
  9. 26
      src/cpp/server/authorization_policy_provider.cc
  10. 2
      src/ruby/ext/grpc/rb_grpc_imports.generated.c
  11. 3
      src/ruby/ext/grpc/rb_grpc_imports.generated.h
  12. 562
      test/core/end2end/tests/sdk_authz.cc
  13. 5
      test/core/security/BUILD
  14. 21
      test/core/security/authorization/test_policies/BUILD
  15. 1
      test/core/security/authorization/test_policies/invalid_policy.json
  16. 40
      test/core/security/authorization/test_policies/valid_policy_1.json
  17. 26
      test/core/security/authorization/test_policies/valid_policy_2.json
  18. 196
      test/core/security/grpc_authorization_policy_provider_test.cc
  19. 1
      test/core/surface/public_headers_must_be_c89.c
  20. 403
      test/cpp/end2end/sdk_authz_end2end_test.cc
  21. 5
      test/cpp/server/BUILD
  22. 45
      test/cpp/server/authorization_policy_provider_test.cc

@ -3096,6 +3096,7 @@ grpc_cc_library(
language = "c++",
deps = [
"gpr_base",
"grpc_base",
"grpc_matchers",
"grpc_rbac_engine",
"grpc_secure",

1
grpc.def generated

@ -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

@ -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

@ -20,7 +20,7 @@
#include <grpc/status.h>
#include <grpcpp/impl/codegen/grpc_library.h>
// TODO(yihuazhang): remove the forward declaration here and include
// TODO(yihuazhang): remove the forward declarations here and include
// <grpc/grpc_security.h> 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<FileWatcherAuthorizationPolicyProvider> 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

@ -27,7 +27,7 @@ struct grpc_authorization_policy_provider
grpc_core::RefCountedPtr<grpc_core::AuthorizationEngine> allow_engine;
grpc_core::RefCountedPtr<grpc_core::AuthorizationEngine> deny_engine;
};
virtual AuthorizationEngines engines() const = 0;
virtual AuthorizationEngines engines() = 0;
};
#endif // GRPC_CORE_LIB_SECURITY_AUTHORIZATION_AUTHORIZATION_POLICY_PROVIDER_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;

@ -19,10 +19,14 @@
#include <grpc/grpc_security.h>
#include <grpc/support/string_util.h>
#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<RefCountedPtr<grpc_authorization_policy_provider>>
StaticDataAuthorizationPolicyProvider::Create(absl::string_view authz_policy) {
auto policies_or = GenerateRbacPolicies(authz_policy);
@ -40,6 +44,113 @@ StaticDataAuthorizationPolicyProvider::StaticDataAuthorizationPolicyProvider(
deny_engine_(MakeRefCounted<GrpcAuthorizationEngine>(
std::move(policies.deny_policy))) {}
namespace {
absl::StatusOr<std::string> 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<RefCountedPtr<grpc_authorization_policy_provider>>
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<FileWatcherAuthorizationPolicyProvider>(
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<FileWatcherAuthorizationPolicyProvider> provider(
static_cast<FileWatcherAuthorizationPolicyProvider*>(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<grpc_core::Thread>(
"FileWatcherAuthorizationPolicyProvider_refreshing_thread", thread_lambda,
WeakRef().release());
refresh_thread_->Start();
}
absl::Status FileWatcherAuthorizationPolicyProvider::ForceUpdate() {
absl::StatusOr<std::string> 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<GrpcAuthorizationEngine>(
std::move(rbac_policies_or->allow_policy));
deny_engine_ = MakeRefCounted<GrpcAuthorizationEngine>(
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<void*>(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<grpc_status_code>(provider_or.status().code());
*error_details =
gpr_strdup(std::string(provider_or.status().message()).c_str());
return nullptr;
}
return provider_or->release();
}

@ -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<RefCountedPtr<grpc_authorization_policy_provider>>
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<AuthorizationEngine> 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<RefCountedPtr<grpc_authorization_policy_provider>>
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<Thread> refresh_thread_;
gpr_event shutdown_event_;
grpc_core::Mutex mu_;
// Engines created using authz_policy_.
RefCountedPtr<AuthorizationEngine> allow_engine_ ABSL_GUARDED_BY(mu_);
RefCountedPtr<AuthorizationEngine> deny_engine_ ABSL_GUARDED_BY(mu_);
};
} // namespace grpc_core

@ -14,6 +14,7 @@
#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpcpp/security/authorization_policy_provider.h>
namespace grpc {
@ -22,7 +23,7 @@ namespace experimental {
std::shared_ptr<StaticDataAuthorizationPolicyProvider>
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>
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<grpc::StatusCode>(code), error_details);
gpr_free(const_cast<char*>(error_details));
return nullptr;
}
return std::make_shared<FileWatcherAuthorizationPolicyProvider>(provider);
}
FileWatcherAuthorizationPolicyProvider::
~FileWatcherAuthorizationPolicyProvider() {
grpc_authorization_policy_provider_release(c_provider_);
}
} // namespace experimental
} // namespace grpc

@ -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");

@ -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

@ -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<void*>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<size_t>(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<char*>(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<char*>(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<char*>(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) {}

@ -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 = [

@ -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",
])

@ -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"
]
}
}
]
}

@ -0,0 +1,26 @@
{
"name": "authz",
"allow_rules":
[
{
"name": "allow_foo",
"request":
{
"paths":
[
"*/foo"
]
}
},
{
"name": "allow_bar",
"request":
{
"paths":
[
"*/bar"
]
}
}
]
}

@ -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<GrpcAuthorizationEngine*>(engines.allow_engine.get())
->action(),
Rbac::Action::kAllow);
ASSERT_NE(engines.deny_engine, nullptr);
EXPECT_EQ(dynamic_cast<GrpcAuthorizationEngine*>(engines.deny_engine.get())
->action(),
Rbac::Action::kDeny);
auto* allow_engine =
dynamic_cast<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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::TmpFile>(
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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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::TmpFile>(
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::TmpFile>(
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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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::TmpFile>(
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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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::TmpFile>(
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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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<GrpcAuthorizationEngine*>(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;
}

@ -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);

@ -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<experimental::AuthorizationPolicyProviderInterface>
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<Channel> 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

@ -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",
],

@ -17,22 +17,21 @@
#include <grpcpp/security/authorization_policy_provider.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 {
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::TmpFile>(
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::TmpFile>(
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.");

Loading…
Cancel
Save