mirror of https://github.com/grpc/grpc.git
parent
2d1ca227a6
commit
1ff1f8fd83
27 changed files with 1256 additions and 18 deletions
@ -0,0 +1,159 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/security/authorization/sdk_server_authz_filter.h" |
||||
|
||||
#include "src/core/lib/security/authorization/evaluate_args.h" |
||||
#include "src/core/lib/transport/transport.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
TraceFlag grpc_sdk_authz_trace(false, "sdk_authz"); |
||||
|
||||
SdkServerAuthzFilter::SdkServerAuthzFilter( |
||||
RefCountedPtr<grpc_auth_context> auth_context, grpc_endpoint* endpoint, |
||||
RefCountedPtr<grpc_authorization_policy_provider> provider) |
||||
: auth_context_(std::move(auth_context)), |
||||
per_channel_evaluate_args_(auth_context_.get(), endpoint), |
||||
provider_(std::move(provider)) {} |
||||
|
||||
grpc_error_handle SdkServerAuthzFilter::Init(grpc_channel_element* elem, |
||||
grpc_channel_element_args* args) { |
||||
GPR_ASSERT(!args->is_last); |
||||
grpc_auth_context* auth_context = |
||||
grpc_find_auth_context_in_args(args->channel_args); |
||||
grpc_authorization_policy_provider* provider = |
||||
grpc_channel_args_find_pointer<grpc_authorization_policy_provider>( |
||||
args->channel_args, GRPC_ARG_AUTHORIZATION_POLICY_PROVIDER); |
||||
if (provider == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Failed to get authorization provider."); |
||||
} |
||||
// grpc_endpoint isn't needed because the current SDK authorization policy
|
||||
// does not support any rules that requires looking for source or destination
|
||||
// addresses.
|
||||
new (elem->channel_data) SdkServerAuthzFilter( |
||||
auth_context != nullptr ? auth_context->Ref() : nullptr, |
||||
/*endpoint=*/nullptr, provider->Ref()); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
void SdkServerAuthzFilter::Destroy(grpc_channel_element* elem) { |
||||
auto* chand = static_cast<SdkServerAuthzFilter*>(elem->channel_data); |
||||
chand->~SdkServerAuthzFilter(); |
||||
} |
||||
|
||||
SdkServerAuthzFilter::CallData::CallData(grpc_call_element* elem) { |
||||
GRPC_CLOSURE_INIT(&recv_initial_metadata_ready_, RecvInitialMetadataReady, |
||||
elem, grpc_schedule_on_exec_ctx); |
||||
} |
||||
|
||||
void SdkServerAuthzFilter::CallData::StartTransportStreamOpBatch( |
||||
grpc_call_element* elem, grpc_transport_stream_op_batch* batch) { |
||||
auto* calld = static_cast<CallData*>(elem->call_data); |
||||
if (batch->recv_initial_metadata) { |
||||
// Inject our callback.
|
||||
calld->recv_initial_metadata_batch_ = |
||||
batch->payload->recv_initial_metadata.recv_initial_metadata; |
||||
calld->original_recv_initial_metadata_ready_ = |
||||
batch->payload->recv_initial_metadata.recv_initial_metadata_ready; |
||||
batch->payload->recv_initial_metadata.recv_initial_metadata_ready = |
||||
&calld->recv_initial_metadata_ready_; |
||||
} |
||||
grpc_call_next_op(elem, batch); |
||||
} |
||||
|
||||
grpc_error_handle SdkServerAuthzFilter::CallData::Init( |
||||
grpc_call_element* elem, const grpc_call_element_args*) { |
||||
new (elem->call_data) CallData(elem); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
void SdkServerAuthzFilter::CallData::Destroy( |
||||
grpc_call_element* elem, const grpc_call_final_info* /*final_info*/, |
||||
grpc_closure* /*ignored*/) { |
||||
CallData* calld = static_cast<CallData*>(elem->call_data); |
||||
calld->~CallData(); |
||||
} |
||||
|
||||
bool SdkServerAuthzFilter::CallData::IsAuthorized(SdkServerAuthzFilter* chand) { |
||||
EvaluateArgs args(recv_initial_metadata_batch_, |
||||
&chand->per_channel_evaluate_args_); |
||||
grpc_authorization_policy_provider::AuthorizationEngines engines = |
||||
chand->provider_->engines(); |
||||
if (engines.deny_engine != nullptr) { |
||||
AuthorizationEngine::Decision decision = |
||||
engines.deny_engine->Evaluate(args); |
||||
if (decision.type == AuthorizationEngine::Decision::Type::kDeny) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_sdk_authz_trace)) { |
||||
gpr_log(GPR_INFO, "chand=%p calld=%p: request denied by policy %s.", |
||||
chand, this, decision.matching_policy_name.c_str()); |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
if (engines.allow_engine != nullptr) { |
||||
AuthorizationEngine::Decision decision = |
||||
engines.allow_engine->Evaluate(args); |
||||
if (decision.type == AuthorizationEngine::Decision::Type::kAllow) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_sdk_authz_trace)) { |
||||
gpr_log(GPR_INFO, "chand=%p calld=%p: request allowed by policy %s.", |
||||
chand, this, decision.matching_policy_name.c_str()); |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_sdk_authz_trace)) { |
||||
gpr_log(GPR_INFO, |
||||
"chand=%p calld=%p: request denied, no matching policy found.", |
||||
chand, this); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
void SdkServerAuthzFilter::CallData::RecvInitialMetadataReady( |
||||
void* arg, grpc_error_handle error) { |
||||
grpc_call_element* elem = static_cast<grpc_call_element*>(arg); |
||||
auto* chand = static_cast<SdkServerAuthzFilter*>(elem->channel_data); |
||||
auto* calld = static_cast<CallData*>(elem->call_data); |
||||
if (error == GRPC_ERROR_NONE) { |
||||
if (!calld->IsAuthorized(chand)) { |
||||
error = grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Unauthorized RPC request rejected."), |
||||
GRPC_ERROR_INT_GRPC_STATUS, |
||||
GRPC_STATUS_PERMISSION_DENIED); |
||||
} |
||||
} else { |
||||
GRPC_ERROR_REF(error); |
||||
} |
||||
Closure::Run(DEBUG_LOCATION, calld->original_recv_initial_metadata_ready_, |
||||
error); |
||||
} |
||||
|
||||
const grpc_channel_filter SdkServerAuthzFilter::kFilterVtable = { |
||||
SdkServerAuthzFilter::CallData::StartTransportStreamOpBatch, |
||||
grpc_channel_next_op, |
||||
sizeof(SdkServerAuthzFilter::CallData), |
||||
SdkServerAuthzFilter::CallData::Init, |
||||
grpc_call_stack_ignore_set_pollset_or_pollset_set, |
||||
SdkServerAuthzFilter::CallData::Destroy, |
||||
sizeof(SdkServerAuthzFilter), |
||||
SdkServerAuthzFilter::Init, |
||||
SdkServerAuthzFilter::Destroy, |
||||
grpc_channel_next_get_info, |
||||
"sdk-server-authz"}; |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,67 @@ |
||||
// 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.
|
||||
|
||||
#ifndef GRPC_CORE_LIB_SECURITY_AUTHORIZATION_SDK_SERVER_AUTHZ_FILTER_H |
||||
#define GRPC_CORE_LIB_SECURITY_AUTHORIZATION_SDK_SERVER_AUTHZ_FILTER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/channel/channel_stack.h" |
||||
#include "src/core/lib/security/authorization/authorization_policy_provider.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class SdkServerAuthzFilter { |
||||
public: |
||||
static const grpc_channel_filter kFilterVtable; |
||||
|
||||
private: |
||||
class CallData { |
||||
public: |
||||
static void StartTransportStreamOpBatch( |
||||
grpc_call_element* elem, grpc_transport_stream_op_batch* batch); |
||||
static grpc_error_handle Init(grpc_call_element* elem, |
||||
const grpc_call_element_args*); |
||||
static void Destroy(grpc_call_element* elem, |
||||
const grpc_call_final_info* /*final_info*/, |
||||
grpc_closure* /*ignored*/); |
||||
|
||||
private: |
||||
explicit CallData(grpc_call_element* elem); |
||||
|
||||
bool IsAuthorized(SdkServerAuthzFilter* chand); |
||||
|
||||
static void RecvInitialMetadataReady(void* arg, grpc_error_handle error); |
||||
|
||||
grpc_metadata_batch* recv_initial_metadata_batch_; |
||||
grpc_closure* original_recv_initial_metadata_ready_; |
||||
grpc_closure recv_initial_metadata_ready_; |
||||
}; |
||||
|
||||
SdkServerAuthzFilter( |
||||
RefCountedPtr<grpc_auth_context> auth_context, grpc_endpoint* endpoint, |
||||
RefCountedPtr<grpc_authorization_policy_provider> provider); |
||||
|
||||
static grpc_error_handle Init(grpc_channel_element* elem, |
||||
grpc_channel_element_args* args); |
||||
static void Destroy(grpc_channel_element* elem); |
||||
|
||||
RefCountedPtr<grpc_auth_context> auth_context_; |
||||
EvaluateArgs::PerChannelArgs per_channel_evaluate_args_; |
||||
RefCountedPtr<grpc_authorization_policy_provider> provider_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_SECURITY_AUTHORIZATION_SDK_SERVER_AUTHZ_FILTER_H
|
@ -0,0 +1,439 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "test/core/end2end/end2end_tests.h" |
||||
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
#include <grpc/byte_buffer.h> |
||||
#include <grpc/grpc_security.h> |
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
#include <grpc/support/time.h> |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/gpr/string.h" |
||||
#include "src/core/lib/security/authorization/grpc_authorization_policy_provider.h" |
||||
#include "src/core/lib/security/credentials/credentials.h" |
||||
#include "test/core/end2end/cq_verifier.h" |
||||
|
||||
static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); } |
||||
|
||||
static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config, |
||||
const char* test_name, |
||||
grpc_channel_args* client_args, |
||||
grpc_channel_args* server_args) { |
||||
grpc_end2end_test_fixture f; |
||||
gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name); |
||||
f = config.create_fixture(client_args, server_args); |
||||
config.init_server(&f, server_args); |
||||
config.init_client(&f, client_args); |
||||
return f; |
||||
} |
||||
|
||||
static gpr_timespec n_seconds_from_now(int n) { |
||||
return grpc_timeout_seconds_to_deadline(n); |
||||
} |
||||
|
||||
static gpr_timespec five_seconds_from_now(void) { |
||||
return n_seconds_from_now(5); |
||||
} |
||||
|
||||
static void drain_cq(grpc_completion_queue* cq) { |
||||
grpc_event ev; |
||||
do { |
||||
ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr); |
||||
} while (ev.type != GRPC_QUEUE_SHUTDOWN); |
||||
} |
||||
|
||||
static void shutdown_server(grpc_end2end_test_fixture* f) { |
||||
if (!f->server) return; |
||||
grpc_server_shutdown_and_notify(f->server, f->shutdown_cq, tag(1000)); |
||||
GPR_ASSERT(grpc_completion_queue_pluck(f->shutdown_cq, tag(1000), |
||||
grpc_timeout_seconds_to_deadline(5), |
||||
nullptr) |
||||
.type == GRPC_OP_COMPLETE); |
||||
grpc_server_destroy(f->server); |
||||
f->server = nullptr; |
||||
} |
||||
|
||||
static void shutdown_client(grpc_end2end_test_fixture* f) { |
||||
if (!f->client) return; |
||||
grpc_channel_destroy(f->client); |
||||
f->client = nullptr; |
||||
} |
||||
|
||||
static void end_test(grpc_end2end_test_fixture* f) { |
||||
shutdown_server(f); |
||||
shutdown_client(f); |
||||
|
||||
grpc_completion_queue_shutdown(f->cq); |
||||
drain_cq(f->cq); |
||||
grpc_completion_queue_destroy(f->cq); |
||||
grpc_completion_queue_destroy(f->shutdown_cq); |
||||
} |
||||
|
||||
static void test_allow_authorized_request(grpc_end2end_test_config config) { |
||||
grpc_call* c; |
||||
grpc_call* s; |
||||
grpc_op ops[6]; |
||||
grpc_op* op; |
||||
grpc_metadata_array initial_metadata_recv; |
||||
grpc_metadata_array trailing_metadata_recv; |
||||
grpc_metadata_array request_metadata_recv; |
||||
grpc_call_details call_details; |
||||
grpc_status_code status; |
||||
const char* error_string = nullptr; |
||||
grpc_call_error error; |
||||
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(); |
||||
c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq, |
||||
grpc_slice_from_static_string("/foo"), nullptr, |
||||
deadline, nullptr); |
||||
GPR_ASSERT(c); |
||||
|
||||
grpc_metadata_array_init(&initial_metadata_recv); |
||||
grpc_metadata_array_init(&trailing_metadata_recv); |
||||
grpc_metadata_array_init(&request_metadata_recv); |
||||
grpc_call_details_init(&call_details); |
||||
|
||||
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); |
||||
|
||||
error = |
||||
grpc_server_request_call(f.server, &s, &call_details, |
||||
&request_metadata_recv, f.cq, f.cq, tag(101)); |
||||
GPR_ASSERT(GRPC_CALL_OK == error); |
||||
CQ_EXPECT_COMPLETION(cqv, tag(101), 1); |
||||
cq_verify(cqv); |
||||
|
||||
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_STATUS_FROM_SERVER; |
||||
op->data.send_status_from_server.trailing_metadata_count = 0; |
||||
op->data.send_status_from_server.status = GRPC_STATUS_OK; |
||||
grpc_slice status_details = grpc_slice_from_static_string("xyz"); |
||||
op->data.send_status_from_server.status_details = &status_details; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
op->op = GRPC_OP_RECV_CLOSE_ON_SERVER; |
||||
op->data.recv_close_on_server.cancelled = &was_cancelled; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102), |
||||
nullptr); |
||||
GPR_ASSERT(GRPC_CALL_OK == error); |
||||
|
||||
CQ_EXPECT_COMPLETION(cqv, tag(102), 1); |
||||
CQ_EXPECT_COMPLETION(cqv, tag(1), 1); |
||||
cq_verify(cqv); |
||||
GPR_ASSERT(GRPC_STATUS_OK == status); |
||||
GPR_ASSERT(0 == grpc_slice_str_cmp(details, "xyz")); |
||||
|
||||
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); |
||||
grpc_metadata_array_destroy(&request_metadata_recv); |
||||
grpc_call_details_destroy(&call_details); |
||||
|
||||
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) { |
||||
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\"," |
||||
" \"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, |
||||
deadline, nullptr); |
||||
GPR_ASSERT(c); |
||||
|
||||
grpc_metadata_array_init(&initial_metadata_recv); |
||||
grpc_metadata_array_init(&trailing_metadata_recv); |
||||
|
||||
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); |
||||
|
||||
GPR_ASSERT(GRPC_STATUS_PERMISSION_DENIED == status); |
||||
GPR_ASSERT(0 == |
||||
grpc_slice_str_cmp(details, "Unauthorized RPC request rejected.")); |
||||
|
||||
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); |
||||
|
||||
grpc_call_unref(c); |
||||
cq_verifier_destroy(cqv); |
||||
|
||||
end_test(&f); |
||||
config.tear_down_data(&f); |
||||
} |
||||
|
||||
static void test_deny_request_no_match_in_policy( |
||||
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\"," |
||||
" \"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_deny_request_no_match_in_policy", 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, |
||||
deadline, nullptr); |
||||
GPR_ASSERT(c); |
||||
|
||||
grpc_metadata_array_init(&initial_metadata_recv); |
||||
grpc_metadata_array_init(&trailing_metadata_recv); |
||||
|
||||
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); |
||||
|
||||
GPR_ASSERT(GRPC_STATUS_PERMISSION_DENIED == status); |
||||
GPR_ASSERT(0 == |
||||
grpc_slice_str_cmp(details, "Unauthorized RPC request rejected.")); |
||||
|
||||
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); |
||||
|
||||
grpc_call_unref(c); |
||||
cq_verifier_destroy(cqv); |
||||
|
||||
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); |
||||
} |
||||
|
||||
void sdk_authz_pre_init(void) {} |
@ -0,0 +1,360 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
|
||||
#include <grpcpp/channel.h> |
||||
#include <grpcpp/client_context.h> |
||||
#include <grpcpp/create_channel.h> |
||||
#include <grpcpp/security/authorization_policy_provider.h> |
||||
#include <grpcpp/server.h> |
||||
#include <grpcpp/server_builder.h> |
||||
|
||||
#include "src/core/lib/security/credentials/fake/fake_credentials.h" |
||||
#include "src/cpp/client/secure_credentials.h" |
||||
#include "src/cpp/server/secure_server_credentials.h" |
||||
#include "src/proto/grpc/testing/echo.grpc.pb.h" |
||||
#include "test/core/util/port.h" |
||||
#include "test/core/util/test_config.h" |
||||
#include "test/cpp/end2end/test_service_impl.h" |
||||
|
||||
namespace grpc { |
||||
namespace testing { |
||||
namespace { |
||||
|
||||
constexpr char kMessage[] = "Hello"; |
||||
|
||||
class SdkAuthzEnd2EndTest : public ::testing::Test { |
||||
protected: |
||||
SdkAuthzEnd2EndTest() |
||||
: server_address_( |
||||
absl::StrCat("localhost:", grpc_pick_unused_port_or_die())), |
||||
server_creds_( |
||||
std::shared_ptr<ServerCredentials>(new SecureServerCredentials( |
||||
grpc_fake_transport_security_server_credentials_create()))), |
||||
channel_creds_( |
||||
std::shared_ptr<ChannelCredentials>(new SecureChannelCredentials( |
||||
grpc_fake_transport_security_credentials_create()))) {} |
||||
|
||||
~SdkAuthzEnd2EndTest() override { server_->Shutdown(); } |
||||
|
||||
// Replaces existing credentials with insecure credentials.
|
||||
void UseInsecureCredentials() { |
||||
server_creds_ = InsecureServerCredentials(); |
||||
channel_creds_ = InsecureChannelCredentials(); |
||||
} |
||||
|
||||
// Creates server with sdk authorization enabled when provider is not null.
|
||||
void InitServer( |
||||
std::shared_ptr<experimental::AuthorizationPolicyProviderInterface> |
||||
provider) { |
||||
ServerBuilder builder; |
||||
builder.AddListeningPort(server_address_, std::move(server_creds_)); |
||||
builder.experimental().SetAuthorizationPolicyProvider(std::move(provider)); |
||||
builder.RegisterService(&service_); |
||||
server_ = builder.BuildAndStart(); |
||||
} |
||||
|
||||
std::shared_ptr<experimental::AuthorizationPolicyProviderInterface> |
||||
CreateStaticAuthzPolicyProvider(const std::string& policy) { |
||||
grpc::Status status; |
||||
auto provider = experimental::StaticDataAuthorizationPolicyProvider::Create( |
||||
policy, &status); |
||||
EXPECT_TRUE(status.ok()); |
||||
return provider; |
||||
} |
||||
|
||||
std::shared_ptr<Channel> BuildChannel() { |
||||
ChannelArguments args; |
||||
return ::grpc::CreateCustomChannel(server_address_, channel_creds_, args); |
||||
} |
||||
|
||||
grpc::Status SendRpc(const std::shared_ptr<Channel>& channel, |
||||
ClientContext* context, |
||||
grpc::testing::EchoResponse* response = nullptr) { |
||||
auto stub = grpc::testing::EchoTestService::NewStub(channel); |
||||
grpc::testing::EchoRequest request; |
||||
request.set_message(kMessage); |
||||
return stub->Echo(context, request, response); |
||||
} |
||||
|
||||
std::string server_address_; |
||||
TestServiceImpl service_; |
||||
std::unique_ptr<Server> server_; |
||||
std::shared_ptr<ServerCredentials> server_creds_; |
||||
std::shared_ptr<ChannelCredentials> channel_creds_; |
||||
}; |
||||
|
||||
TEST_F(SdkAuthzEnd2EndTest, |
||||
StaticInitAllowsRpcRequestNoMatchInDenyMatchInAllow) { |
||||
std::string policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_echo\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/Echo\"" |
||||
" ]," |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \"key-foo\"," |
||||
" \"values\": [\"foo1\", \"foo2\"]" |
||||
" }," |
||||
" {" |
||||
" \"key\": \"key-bar\"," |
||||
" \"values\": [\"bar1\"]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"deny_clientstreamingecho\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/ClientStreamingEcho\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
InitServer(CreateStaticAuthzPolicyProvider(policy)); |
||||
auto channel = BuildChannel(); |
||||
ClientContext context; |
||||
context.AddMetadata("key-foo", "foo2"); |
||||
context.AddMetadata("key-bar", "bar1"); |
||||
context.AddMetadata("key-baz", "baz1"); |
||||
grpc::testing::EchoResponse resp; |
||||
grpc::Status status = SendRpc(channel, &context, &resp); |
||||
EXPECT_TRUE(status.ok()); |
||||
EXPECT_EQ(resp.message(), kMessage); |
||||
} |
||||
|
||||
TEST_F(SdkAuthzEnd2EndTest, StaticInitDeniesRpcRequestNoMatchInAllowAndDeny) { |
||||
std::string policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_foo\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/foo\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"deny_bar\"," |
||||
" \"source\": {" |
||||
" \"principals\": [" |
||||
" \"bar\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
InitServer(CreateStaticAuthzPolicyProvider(policy)); |
||||
auto channel = BuildChannel(); |
||||
ClientContext context; |
||||
grpc::testing::EchoResponse resp; |
||||
grpc::Status status = SendRpc(channel, &context, &resp); |
||||
EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); |
||||
EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); |
||||
EXPECT_TRUE(resp.message().empty()); |
||||
} |
||||
|
||||
TEST_F(SdkAuthzEnd2EndTest, StaticInitDeniesRpcRequestMatchInDenyMatchInAllow) { |
||||
std::string policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_all\"" |
||||
" }" |
||||
" ]," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"deny_echo\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/Echo\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
InitServer(CreateStaticAuthzPolicyProvider(policy)); |
||||
auto channel = BuildChannel(); |
||||
ClientContext context; |
||||
grpc::testing::EchoResponse resp; |
||||
grpc::Status status = SendRpc(channel, &context, &resp); |
||||
EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); |
||||
EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); |
||||
EXPECT_TRUE(resp.message().empty()); |
||||
} |
||||
|
||||
TEST_F(SdkAuthzEnd2EndTest, |
||||
StaticInitDeniesRpcRequestMatchInDenyNoMatchInAllow) { |
||||
std::string policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_clientstreamingecho\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/ClientStreamingEcho\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]," |
||||
" \"deny_rules\": [" |
||||
" {" |
||||
" \"name\": \"deny_echo\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/Echo\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
InitServer(CreateStaticAuthzPolicyProvider(policy)); |
||||
auto channel = BuildChannel(); |
||||
ClientContext context; |
||||
grpc::testing::EchoResponse resp; |
||||
grpc::Status status = SendRpc(channel, &context, &resp); |
||||
EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); |
||||
EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); |
||||
EXPECT_TRUE(resp.message().empty()); |
||||
} |
||||
|
||||
TEST_F(SdkAuthzEnd2EndTest, StaticInitAllowsRpcRequestEmptyDenyMatchInAllow) { |
||||
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\"]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
InitServer(CreateStaticAuthzPolicyProvider(policy)); |
||||
auto channel = BuildChannel(); |
||||
ClientContext context; |
||||
context.AddMetadata("key-foo", "foo2"); |
||||
context.AddMetadata("key-bar", "bar1"); |
||||
context.AddMetadata("key-baz", "baz1"); |
||||
grpc::testing::EchoResponse resp; |
||||
grpc::Status status = SendRpc(channel, &context, &resp); |
||||
EXPECT_TRUE(status.ok()); |
||||
EXPECT_EQ(resp.message(), kMessage); |
||||
} |
||||
|
||||
TEST_F(SdkAuthzEnd2EndTest, StaticInitDeniesRpcRequestEmptyDenyNoMatchInAllow) { |
||||
std::string policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_echo\"," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/Echo\"" |
||||
" ]," |
||||
" \"headers\": [" |
||||
" {" |
||||
" \"key\": \"key-foo\"," |
||||
" \"values\": [\"foo1\"]" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
InitServer(CreateStaticAuthzPolicyProvider(policy)); |
||||
auto channel = BuildChannel(); |
||||
ClientContext context; |
||||
context.AddMetadata("key-bar", "bar1"); |
||||
grpc::testing::EchoResponse resp; |
||||
grpc::Status status = SendRpc(channel, &context, &resp); |
||||
EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); |
||||
EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); |
||||
EXPECT_TRUE(resp.message().empty()); |
||||
} |
||||
|
||||
TEST_F( |
||||
SdkAuthzEnd2EndTest, |
||||
StaticInitDeniesRpcRequestWithPrincipalsFieldOnUnauthenticatedConnection) { |
||||
std::string policy = |
||||
"{" |
||||
" \"name\": \"authz\"," |
||||
" \"allow_rules\": [" |
||||
" {" |
||||
" \"name\": \"allow_echo\"," |
||||
" \"source\": {" |
||||
" \"principals\": [" |
||||
" \"foo\"" |
||||
" ]" |
||||
" }," |
||||
" \"request\": {" |
||||
" \"paths\": [" |
||||
" \"*/Echo\"" |
||||
" ]" |
||||
" }" |
||||
" }" |
||||
" ]" |
||||
"}"; |
||||
UseInsecureCredentials(); |
||||
InitServer(CreateStaticAuthzPolicyProvider(policy)); |
||||
auto channel = BuildChannel(); |
||||
ClientContext context; |
||||
grpc::testing::EchoResponse resp; |
||||
grpc::Status status = SendRpc(channel, &context, &resp); |
||||
EXPECT_EQ(status.error_code(), grpc::StatusCode::PERMISSION_DENIED); |
||||
EXPECT_EQ(status.error_message(), "Unauthorized RPC request rejected."); |
||||
EXPECT_TRUE(resp.message().empty()); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace grpc
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(argc, argv); |
||||
const auto result = RUN_ALL_TESTS(); |
||||
return result; |
||||
} |
Loading…
Reference in new issue