client_channel, client_auth: rewrite disallowed status codes from the control plane (#30789)
* client_channel: rewrite illegal status codes from control plane * rewrite illegal status codes for call creds * move fail_lb policy out of retry_lb_fail test so it can be reused * test resolver and LB policy status rewrites * add test for ConfigSelector status rewriting * attempt to add client_auth filter unit test * fix client_auth_filter test * cleanup test * fix build * fix some memory leaks * Automated change: Fix sanity tests * Update client_auth_filter_test.cc * fix build * code review comments * clang-tidy Co-authored-by: markdroth <markdroth@users.noreply.github.com> Co-authored-by: Craig Tiller <ctiller@google.com>pull/30820/head
parent
2142183ef4
commit
bf9304ef17
15 changed files with 532 additions and 90 deletions
@ -0,0 +1,194 @@ |
|||||||
|
// Copyright 2022 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 "src/core/lib/promise/promise.h" |
||||||
|
#include "src/core/lib/resource_quota/resource_quota.h" |
||||||
|
#include "src/core/lib/security/context/security_context.h" |
||||||
|
#include "src/core/lib/security/credentials/fake/fake_credentials.h" |
||||||
|
#include "src/core/lib/security/security_connector/fake/fake_security_connector.h" |
||||||
|
#include "src/core/lib/security/transport/auth_filters.h" |
||||||
|
#include "test/core/promise/test_context.h" |
||||||
|
|
||||||
|
// TODO(roth): Need to add a lot more tests here. I created this file
|
||||||
|
// as part of adding a feature, and I added tests only for the feature I
|
||||||
|
// was adding. When we have time, we need to go back and write
|
||||||
|
// comprehensive tests for all of the functionality in the filter.
|
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
namespace { |
||||||
|
|
||||||
|
class ClientAuthFilterTest : public ::testing::Test { |
||||||
|
protected: |
||||||
|
class FailCallCreds : public grpc_call_credentials { |
||||||
|
public: |
||||||
|
explicit FailCallCreds(absl::Status status) |
||||||
|
: grpc_call_credentials(GRPC_SECURITY_NONE), |
||||||
|
status_(std::move(status)) {} |
||||||
|
|
||||||
|
UniqueTypeName type() const override { |
||||||
|
static UniqueTypeName::Factory kFactory("FailCallCreds"); |
||||||
|
return kFactory.Create(); |
||||||
|
} |
||||||
|
|
||||||
|
ArenaPromise<absl::StatusOr<ClientMetadataHandle>> GetRequestMetadata( |
||||||
|
ClientMetadataHandle /*initial_metadata*/, |
||||||
|
const GetRequestMetadataArgs* /*args*/) override { |
||||||
|
return Immediate<absl::StatusOr<ClientMetadataHandle>>(status_); |
||||||
|
} |
||||||
|
|
||||||
|
int cmp_impl(const grpc_call_credentials* other) const override { |
||||||
|
return QsortCompare( |
||||||
|
status_.ToString(), |
||||||
|
static_cast<const FailCallCreds*>(other)->status_.ToString()); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
absl::Status status_; |
||||||
|
}; |
||||||
|
|
||||||
|
ClientAuthFilterTest() |
||||||
|
: memory_allocator_( |
||||||
|
ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator( |
||||||
|
"test")), |
||||||
|
arena_(MakeScopedArena(1024, &memory_allocator_)), |
||||||
|
initial_metadata_batch_(arena_.get()), |
||||||
|
trailing_metadata_batch_(arena_.get()), |
||||||
|
target_(Slice::FromStaticString("localhost:1234")), |
||||||
|
channel_creds_(grpc_fake_transport_security_credentials_create()) { |
||||||
|
initial_metadata_batch_.Set(HttpAuthorityMetadata(), target_.Ref()); |
||||||
|
} |
||||||
|
|
||||||
|
~ClientAuthFilterTest() override { |
||||||
|
for (size_t i = 0; i < GRPC_CONTEXT_COUNT; ++i) { |
||||||
|
if (call_context_[i].destroy != nullptr) { |
||||||
|
call_context_[i].destroy(call_context_[i].value); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ChannelArgs MakeChannelArgs(absl::Status status_for_call_creds) { |
||||||
|
ChannelArgs args; |
||||||
|
auto security_connector = channel_creds_->create_security_connector( |
||||||
|
status_for_call_creds.ok() |
||||||
|
? nullptr |
||||||
|
: MakeRefCounted<FailCallCreds>(std::move(status_for_call_creds)), |
||||||
|
std::string(target_.as_string_view()).c_str(), &args); |
||||||
|
auto auth_context = MakeRefCounted<grpc_auth_context>(nullptr); |
||||||
|
absl::string_view security_level = "TSI_SECURITY_NONE"; |
||||||
|
auth_context->add_property(GRPC_TRANSPORT_SECURITY_LEVEL_PROPERTY_NAME, |
||||||
|
security_level.data(), security_level.size()); |
||||||
|
return args.SetObject(std::move(security_connector)) |
||||||
|
.SetObject(std::move(auth_context)); |
||||||
|
} |
||||||
|
|
||||||
|
MemoryAllocator memory_allocator_; |
||||||
|
ScopedArenaPtr arena_; |
||||||
|
grpc_metadata_batch initial_metadata_batch_; |
||||||
|
grpc_metadata_batch trailing_metadata_batch_; |
||||||
|
Slice target_; |
||||||
|
RefCountedPtr<grpc_channel_credentials> channel_creds_; |
||||||
|
grpc_call_context_element call_context_[GRPC_CONTEXT_COUNT]; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_F(ClientAuthFilterTest, CreateFailsWithoutRequiredChannelArgs) { |
||||||
|
EXPECT_FALSE( |
||||||
|
ClientAuthFilter::Create(ChannelArgs(), ChannelFilter::Args()).ok()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(ClientAuthFilterTest, CreateSucceeds) { |
||||||
|
auto filter = ClientAuthFilter::Create(MakeChannelArgs(absl::OkStatus()), |
||||||
|
ChannelFilter::Args()); |
||||||
|
EXPECT_TRUE(filter.ok()) << filter.status(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(ClientAuthFilterTest, CallCredsFails) { |
||||||
|
auto filter = ClientAuthFilter::Create( |
||||||
|
MakeChannelArgs(absl::UnauthenticatedError("access denied")), |
||||||
|
ChannelFilter::Args()); |
||||||
|
// TODO(ctiller): use Activity here, once it's ready.
|
||||||
|
TestContext<Arena> context(arena_.get()); |
||||||
|
TestContext<grpc_call_context_element> promise_call_context(call_context_); |
||||||
|
auto promise = filter->MakeCallPromise( |
||||||
|
CallArgs{ |
||||||
|
ClientMetadataHandle::TestOnlyWrap(&initial_metadata_batch_), |
||||||
|
nullptr, |
||||||
|
}, |
||||||
|
[&](CallArgs /*call_args*/) { |
||||||
|
return ArenaPromise<ServerMetadataHandle>( |
||||||
|
[&]() -> Poll<ServerMetadataHandle> { |
||||||
|
return ServerMetadataHandle::TestOnlyWrap( |
||||||
|
&trailing_metadata_batch_); |
||||||
|
}); |
||||||
|
}); |
||||||
|
auto result = promise(); |
||||||
|
ServerMetadataHandle* server_metadata = |
||||||
|
absl::get_if<ServerMetadataHandle>(&result); |
||||||
|
ASSERT_TRUE(server_metadata != nullptr); |
||||||
|
auto status_md = (*server_metadata)->get(GrpcStatusMetadata()); |
||||||
|
ASSERT_TRUE(status_md.has_value()); |
||||||
|
EXPECT_EQ(*status_md, GRPC_STATUS_UNAUTHENTICATED); |
||||||
|
const Slice* message_md = |
||||||
|
(*server_metadata)->get_pointer(GrpcMessageMetadata()); |
||||||
|
ASSERT_TRUE(message_md != nullptr); |
||||||
|
EXPECT_EQ(message_md->as_string_view(), "access denied"); |
||||||
|
(*server_metadata)->~ServerMetadata(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(ClientAuthFilterTest, RewritesInvalidStatusFromCallCreds) { |
||||||
|
auto filter = ClientAuthFilter::Create( |
||||||
|
MakeChannelArgs(absl::AbortedError("nope")), ChannelFilter::Args()); |
||||||
|
// TODO(ctiller): use Activity here, once it's ready.
|
||||||
|
TestContext<Arena> context(arena_.get()); |
||||||
|
TestContext<grpc_call_context_element> promise_call_context(call_context_); |
||||||
|
auto promise = filter->MakeCallPromise( |
||||||
|
CallArgs{ |
||||||
|
ClientMetadataHandle::TestOnlyWrap(&initial_metadata_batch_), |
||||||
|
nullptr, |
||||||
|
}, |
||||||
|
[&](CallArgs /*call_args*/) { |
||||||
|
return ArenaPromise<ServerMetadataHandle>( |
||||||
|
[&]() -> Poll<ServerMetadataHandle> { |
||||||
|
return ServerMetadataHandle::TestOnlyWrap( |
||||||
|
&trailing_metadata_batch_); |
||||||
|
}); |
||||||
|
}); |
||||||
|
auto result = promise(); |
||||||
|
ServerMetadataHandle* server_metadata = |
||||||
|
absl::get_if<ServerMetadataHandle>(&result); |
||||||
|
ASSERT_TRUE(server_metadata != nullptr); |
||||||
|
auto status_md = (*server_metadata)->get(GrpcStatusMetadata()); |
||||||
|
ASSERT_TRUE(status_md.has_value()); |
||||||
|
EXPECT_EQ(*status_md, GRPC_STATUS_INTERNAL); |
||||||
|
const Slice* message_md = |
||||||
|
(*server_metadata)->get_pointer(GrpcMessageMetadata()); |
||||||
|
ASSERT_TRUE(message_md != nullptr); |
||||||
|
EXPECT_EQ(message_md->as_string_view(), |
||||||
|
"Illegal status code from call credentials; original status: " |
||||||
|
"ABORTED: nope"); |
||||||
|
(*server_metadata)->~ServerMetadata(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
grpc_init(); |
||||||
|
int retval = RUN_ALL_TESTS(); |
||||||
|
grpc_shutdown(); |
||||||
|
return retval; |
||||||
|
} |
Loading…
Reference in new issue