blob: 0ac72ef5a803770e742bf3689a666de6368b9394 [file] [log] [blame]
#include "resource_authz.h"
#include "net/proto2/contrib/parse_proto/parse_text_proto.h"
#include "gmock.h"
#include "gunit.h"
#include "testing/fuzzing/fuzztest.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "fake_auth_context.h"
#include "proxy_config.pb.h"
#include "redfish_plugin.h"
namespace milotic {
namespace {
using ::testing::AtMost;
using ::testing::HasSubstr;
using ::testing::Ref;
using ::testing::Return;
using ::testing::status::StatusIs;
using ::milotic_grpc_proxy::AuthorizationPolicy;
using ::google::protobuf::contrib::parse_proto::ParseTextProtoOrDie;
using RequestVerb = RedfishPlugin::RequestVerb;
TEST(ResourceAuthzTest, EmptyRulePassesMatchingResource) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
resource_path: "/some/resource"
allow: { key: "empty_rule" }
}
)pb");
milotic::FakeAuthContext auth_context(false);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_OK(
AuthorizeRequest(policy, RequestVerb::kGet, "/some/resource", context));
}
TEST(ResourceAuthzTest, EmptyRuleRejectsNonMatchingResource) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
resource_path: "/some/resource"
allow: { key: "empty_rule" }
})pb");
milotic::FakeAuthContext auth_context(false);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_THAT(AuthorizeRequest(policy, RequestVerb::kGet,
"/some/other/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
TEST(ResourceAuthzTest, NoAllowBlocksAll) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
resource_path: "/some/resource"
}
)pb");
milotic::FakeAuthContext auth_context(true);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_THAT(
AuthorizeRequest(policy, RequestVerb::kGet, "/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
TEST(ResourceAuthzTest, WithSubtreeWorks) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
resource_path: "/some"
with_subtree: true
allow: { key: "all" }
}
)pb");
milotic::FakeAuthContext auth_context(true);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_OK(
AuthorizeRequest(policy, RequestVerb::kGet, "/some/resource", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet, "/some/other/resource",
context));
EXPECT_THAT(AuthorizeRequest(policy, RequestVerb::kGet,
"/some_blocked/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
TEST(ResourceAuthzTest, ExpandBlockedIfWithoutSubtree) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
resource_path: "/some"
allow: { key: "all" }
}
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
resource_path: "/some/allowed"
with_subtree: true
allow: { key: "all" }
}
)pb");
milotic::FakeAuthContext auth_context(true);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_THAT(AuthorizeRequest(policy, RequestVerb::kGet,
"/some?$expand=*&$select=a", context),
StatusIs(absl::StatusCode::kPermissionDenied));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet,
"/some/allowed/resource?$expand=*", context));
}
TEST(ResourceAuthzTest, NonExpandQueryParametersIgnored) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
resource_path: "/some"
allow: { key: "all" }
}
)pb");
milotic::FakeAuthContext auth_context(true);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_OK(
AuthorizeRequest(policy, RequestVerb::kGet, "/some?$select=a", context));
}
TEST(ResourceAuthzTest, MappingsAppliedInSequence) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping1"
request_verb: REQUEST_VERB_GET
resource_path: "/some/resource"
}
mappings: {
name: "test_mapping2"
request_verb: REQUEST_VERB_GET
resource_path: "/some"
with_subtree: true
allow: { key: "all" }
}
)pb");
milotic::FakeAuthContext auth_context(true);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_THAT(
AuthorizeRequest(policy, RequestVerb::kGet, "/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet, "/some/other/resource",
context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet,
"/some/resource/allowed_subresource", context));
}
TEST(ResourceAuthzTest, NoVerbMatchesAll) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "always_reject_some_resource"
resource_path: "/some/resource"
}
mappings: {
name: "accept_others"
with_subtree: true
allow: { key: "all" }
resource_path: "/"
}
)pb");
milotic::FakeAuthContext auth_context(false);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet,
"/some/allowed/resource", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kPost,
"/some/allowed/resource", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kPatch,
"/some/allowed/resource", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kPut,
"/some/allowed/resource", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kDelete,
"/some/allowed/resource", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kSubscribe,
"/some/allowed/resource", context));
EXPECT_THAT(
AuthorizeRequest(policy, RequestVerb::kGet, "/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied,
HasSubstr("always_reject_some_resource")));
EXPECT_THAT(
AuthorizeRequest(policy, RequestVerb::kPost, "/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied,
HasSubstr("always_reject_some_resource")));
EXPECT_THAT(
AuthorizeRequest(policy, RequestVerb::kPatch, "/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied,
HasSubstr("always_reject_some_resource")));
EXPECT_THAT(
AuthorizeRequest(policy, RequestVerb::kPut, "/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied,
HasSubstr("always_reject_some_resource")));
EXPECT_THAT(
AuthorizeRequest(policy, RequestVerb::kDelete, "/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied,
HasSubstr("always_reject_some_resource")));
EXPECT_THAT(AuthorizeRequest(policy, RequestVerb::kSubscribe,
"/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied,
HasSubstr("always_reject_some_resource")));
}
TEST(ResourceAuthzTest, RootResourcePathMatchesAll) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
request_verb: REQUEST_VERB_GET
allow: { key: "all" }
resource_path: "/"
with_subtree: true
}
)pb");
milotic::FakeAuthContext auth_context(true);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet, "/some", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet, "/other", context));
EXPECT_THAT(AuthorizeRequest(policy, RequestVerb::kPost, "/other", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
TEST(ResourceAuthzTest, TrailingSlashMatches) {
AuthorizationPolicy policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "some_rule"
resource_path: "/some/resource"
allow: { key: "all" }
}
)pb");
milotic::FakeAuthContext auth_context(true);
AuthorizationContext context{.grpc_context = auth_context};
EXPECT_OK(
AuthorizeRequest(policy, RequestVerb::kGet, "/some/resource/", context));
EXPECT_OK(AuthorizeRequest(policy, RequestVerb::kGet,
"/some/resource/?$select=a", context));
}
TEST(ResourceAuthzTest, NoPermissionCheckerFailsPermissionCheck) {
FakeAuthContext fake_auth_context(true);
AuthorizationContext context{.grpc_context = fake_auth_context};
AuthorizationPolicy authorization_policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
allow: {
key: "test_rule",
value: { permission_id: "test-permission" }
}
resource_path: "/"
with_subtree: true
}
)pb");
EXPECT_THAT(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
class MockPermissionChecker : public PermissionChecker {
public:
MOCK_METHOD(bool, Check,
(const AuthorizationContext& context,
absl::string_view permission_id),
(override));
};
TEST(ResourceAuthzTest, PermissionCheckerCanApprove) {
FakeAuthContext fake_auth_context(true);
MockPermissionChecker permission_checker;
AuthorizationContext context{.grpc_context = fake_auth_context,
.permission_checker = &permission_checker};
AuthorizationPolicy authorization_policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
allow: {
key: "test_rule",
value: { permission_id: "test-permission" }
}
resource_path: "/"
with_subtree: true
}
)pb");
EXPECT_CALL(permission_checker, Check(Ref(context), "test-permission"))
.WillOnce(Return(true));
EXPECT_OK(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/resource", context));
}
TEST(ResourceAuthzTest, PermissionCheckerCanReject) {
FakeAuthContext fake_auth_context(true);
MockPermissionChecker permission_checker;
AuthorizationContext context{.grpc_context = fake_auth_context,
.permission_checker = &permission_checker};
AuthorizationPolicy authorization_policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
allow: {
key: "test_rule",
value: { permission_id: "test-permission" }
}
resource_path: "/"
with_subtree: true
}
)pb");
EXPECT_CALL(permission_checker, Check(Ref(context), "test-permission"))
.WillOnce(Return(false));
EXPECT_THAT(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
TEST(ResourceAuthzTest, LocalPermissionCanAllow) {
FakeAuthContext fake_auth_context(true);
AuthorizationContext context{.grpc_context = fake_auth_context};
AuthorizationPolicy authorization_policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
allow: {
key: "test_rule",
value: { local: true }
}
resource_path: "/"
with_subtree: true
}
)pb");
fake_auth_context.AddProperty("transport_security_type", "local");
EXPECT_OK(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/resource", context));
}
TEST(ResourceAuthzTest, LocalPermissionCanReject) {
FakeAuthContext fake_auth_context(true);
AuthorizationContext context{.grpc_context = fake_auth_context};
AuthorizationPolicy authorization_policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
allow: {
key: "test_rule",
value: { local: true }
}
resource_path: "/"
with_subtree: true
}
)pb");
EXPECT_THAT(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
fake_auth_context.AddProperty("transport_security_type", "none");
EXPECT_THAT(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
TEST(ResourceAuthzTest, MultipleResourcePathsMatch) {
FakeAuthContext fake_auth_context(true);
MockPermissionChecker permission_checker;
AuthorizationContext context{.grpc_context = fake_auth_context,
.permission_checker = &permission_checker};
AuthorizationPolicy authorization_policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping1"
allow: {
key: "test_rule1",
value: { permission_id: "matching-resources" }
}
resource_path: "/some/resource"
resource_path: "/some/other/resource"
}
mappings: {
name: "test_mapping2"
allow: {
key: "test_rule2"
value: { permission_id: "non-matching-resources" }
}
resource_path: "/"
with_subtree: true
}
)pb");
EXPECT_CALL(permission_checker, Check(Ref(context), "matching-resources"))
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(permission_checker, Check(Ref(context), "non-matching-resources"))
.WillOnce(Return(false));
EXPECT_OK(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/resource", context));
EXPECT_OK(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/other/resource", context));
EXPECT_THAT(AuthorizeRequest(authorization_policy, RequestVerb::kGet,
"/some/non-matching/resource", context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
void RejectedPermissionIsDenied(int verb, absl::string_view resource_id) {
FakeAuthContext fake_auth_context(true);
testing::StrictMock<MockPermissionChecker> permission_checker;
AuthorizationContext context{.grpc_context = fake_auth_context,
.permission_checker = &permission_checker};
AuthorizationPolicy authorization_policy = ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping"
allow: {
key: "test_permission"
value: { permission_id: "reject" }
}
allow: {
key: "test_local"
value: { local: true }
}
resource_path: "/"
with_subtree: true
}
)pb");
EXPECT_CALL(permission_checker, Check(Ref(context), "reject"))
.Times(AtMost(1))
.WillOnce(Return(false));
EXPECT_THAT(
AuthorizeRequest(authorization_policy, static_cast<RequestVerb>(verb),
resource_id, context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
FUZZ_TEST(ResourceAuthzTest, RejectedPermissionIsDenied);
} // namespace
} // namespace milotic