Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Ranger): refactor the logic when ranger performs ACL #1518

Merged
merged 7 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/runtime/ranger/access_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include <cstdint>
#include <type_traits>

#include "utils/enum_helper.h"

namespace dsn {
namespace ranger {

Expand All @@ -35,6 +37,15 @@ enum class access_type : uint8_t
kMetadata = 1 << 5,
kControl = 1 << 6
};
ENUM_BEGIN(access_type, access_type::kInvalid)
ENUM_REG(access_type::kRead)
ENUM_REG(access_type::kWrite)
ENUM_REG(access_type::kCreate)
ENUM_REG(access_type::kDrop)
ENUM_REG(access_type::kList)
ENUM_REG(access_type::kMetadata)
ENUM_REG(access_type::kControl)
ENUM_END(access_type)

using act = std::underlying_type<access_type>::type;

Expand Down
157 changes: 132 additions & 25 deletions src/runtime/ranger/ranger_resource_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "ranger_resource_policy.h"

#include "runtime/ranger/access_type.h"
#include "utils/fmt_logging.h"

namespace dsn {
namespace ranger {
Expand All @@ -27,45 +28,151 @@ bool policy_item::match(const access_type &ac_type, const std::string &user_name
return static_cast<bool>(access_types & ac_type) && users.count(user_name) != 0;
}

bool acl_policies::allowed(const access_type &ac_type, const std::string &user_name) const
policy_check_status acl_policies::policies_check(const access_type &ac_type,
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
const std::string &user_name,
const policy_check_type &check_type) const
{
// 1. Check if it is not allowed.
for (const auto &deny_policy : deny_policies) {
// 1.1. In 'deny_policies'.
if (!deny_policy.match(ac_type, user_name)) {
if (check_type == policy_check_type::kAllow) {
return do_policies_check(
check_type, ac_type, user_name, allow_policies, allow_policies_exclude);
}
CHECK(check_type == policy_check_type::kDeny, "");
return do_policies_check(check_type, ac_type, user_name, deny_policies, deny_policies_exclude);
}

policy_check_status
acl_policies::do_policies_check(const policy_check_type &check_type,
const access_type &ac_type,
const std::string &user_name,
const std::vector<policy_item> &policies,
const std::vector<policy_item> &exclude_policies) const
{
for (const auto &policy : policies) {
// 1. Doesn't match an allow_policies or a deny_policies.
if (!policy.match(ac_type, user_name)) {
continue;
}
bool in_deny_policies_exclude = false;
for (const auto &deny_policy_exclude : deny_policies_exclude) {
if (deny_policy_exclude.match(ac_type, user_name)) {
in_deny_policies_exclude = true;
break;
// 2. Matches a policy.
for (const auto &policy_exclude : exclude_policies) {
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
if (policy_exclude.match(ac_type, user_name)) {
// 2.1. Matches an allow/deny_policies_exclude.
return policy_check_status::kPending;
}
}
// 1.2. Not in any 'deny_policies_exclude', it's not allowed.
if (!in_deny_policies_exclude) {
return false;
// 2.2. Doesn't match any allow/deny_exclude_policies.
if (check_type == policy_check_type::kAllow) {
return policy_check_status::kAllowed;
} else {
return policy_check_status::kDenied;
}
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
}
// 3. Doesn't match any policy.
return policy_check_status::kNotMatched;
}

// 2. Check if it is allowed.
for (const auto &allow_policy : allow_policies) {
// 2.1. In 'allow_policies'.
if (!allow_policy.match(ac_type, user_name)) {
access_control_result
check_ranger_resource_policy_allowed(const std::vector<ranger_resource_policy> &policies,
const access_type &ac_type,
const std::string &user_name,
bool need_match_database,
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
const std::string &database_name,
const std::string &default_database_name)
{
// Check if it is denied by any policy in current resource.
for (const auto &policy : policies) {
if (need_match_database) {
// Lagacy table not match any database.
if (database_name.empty() && policy.database_names.count("*") == 0 &&
policy.database_names.count(default_database_name) == 0) {
continue;
}
// New table not match any database.
if (!database_name.empty() && policy.database_names.count("*") == 0 &&
policy.database_names.count(database_name) == 0) {
continue;
}
}
auto check_status =
policy.policies.policies_check(ac_type, user_name, policy_check_type::kDeny);
// In a 'deny_policies' and not in any 'deny_policies_exclude'.
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
if (policy_check_status::kDenied == check_status) {
return access_control_result::kDenied;
}
// In a 'deny_policies' and in a 'deny_policies_exclude' or not match.
if (policy_check_status::kPending == check_status ||
policy_check_status::kNotMatched == check_status) {
continue;
}
for (const auto &allow_policy_exclude : allow_policies_exclude) {
// 2.2. In some 'allow_policies_exclude', it's not allowed.
if (allow_policy_exclude.match(ac_type, user_name)) {
return false;
}

// Check if it is allowed by any policy in current resource.
for (const auto &policy : policies) {
if (need_match_database) {
// Lagacy table not match any database.
if (database_name.empty() && policy.database_names.count("*") == 0 &&
policy.database_names.count(default_database_name) == 0) {
continue;
}
// New table not match any database.
if (!database_name.empty() && policy.database_names.count("*") == 0 &&
policy.database_names.count(database_name) == 0) {
continue;
}
}
auto check_status =
policy.policies.policies_check(ac_type, user_name, policy_check_type::kAllow);
// In a 'allow_policies' and not in any 'allow_policies_exclude'.
if (policy_check_status::kAllowed == check_status) {
return access_control_result::kAllowed;
}
// In a 'deny_policies' and in a 'deny_policies_exclude' or not match.
if (policy_check_status::kPending == check_status ||
policy_check_status::kNotMatched == check_status) {
continue;
}
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
}
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved

// The check that does not match any policy in current reosource returns false.
return access_control_result::kDenied;
}

access_control_result check_ranger_database_table_policy_allowed(
const std::vector<matched_database_table_policy> &policies,
const access_type &ac_type,
const std::string &user_name)
{
// Check if it is denied by any DATABASE_TABLE policy.
for (const auto &policy : policies) {
auto check_status =
policy.policies.policies_check(ac_type, user_name, policy_check_type::kDeny);
// In a 'deny_policies' and not in any 'deny_policies_exclude'.
if (policy_check_status::kDenied == check_status) {
return access_control_result::kDenied;
}
// In a 'deny_policies' and in a 'deny_policies_exclude' or not match.
if (policy_check_status::kPending == check_status ||
policy_check_status::kNotMatched == check_status) {
continue;
}
}

// Check if it is allowed by any DATABASE_TABLE policy.
for (const auto &policy : policies) {
auto check_status =
policy.policies.policies_check(ac_type, user_name, policy_check_type::kAllow);
// In a 'allow_policies' and not in any 'allow_policies_exclude'.
if (policy_check_status::kAllowed == check_status) {
return access_control_result::kAllowed;
}
// In a 'deny_policies' and in a 'deny_policies_exclude' or not match.
if (policy_check_status::kPending == check_status ||
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
policy_check_status::kNotMatched == check_status) {
continue;
}
// 2.3. Not in any 'allow_policies_exclude', it's allowed.
return true;
}

// 3. Otherwise, it's not allowed.
return false;
// The check that does not match any DATABASE_TABLE policy returns false.
return access_control_result::kDenied;
}

} // namespace ranger
Expand Down
134 changes: 131 additions & 3 deletions src/runtime/ranger/ranger_resource_policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,45 @@

#include "access_type.h"
#include "common/json_helper.h"
#include "utils/enum_helper.h"

namespace dsn {
namespace ranger {

// Types of policy checks.
// kAllow means this checks for 'allow_policies' and 'allow_policies_exclude'.
// kDeny means this checks for 'deny_policies' and 'deny_policies_exclude'.
enum class policy_check_type
{
kAllow = 0,
kDeny,
kInvalid
};
ENUM_BEGIN(policy_check_type, policy_check_type::kInvalid)
ENUM_REG(policy_check_type::kAllow)
ENUM_REG(policy_check_type::kDeny)
ENUM_END(policy_check_type)

// The return status code when a policy('kAllow' or 'kDeny' policy_check_type) is checked.
// kAllowed means in a 'allow_policies' and not in any 'allow_policies_exclude'.
// kDenied means in a 'deny_policies' and not in any 'deny_policies_exclude'.
// kNotMatched means not match any 'allow_policies' or 'deny_policies'.
// kPending means in a 'allow_policies/deny_policies' and in a
// 'allow_policies_exclude/deny_policies_exclude'.
enum class policy_check_status
{
kAllowed = 0,
kDenied,
kNotMatched,
kPending
};

enum class access_control_result
{
kAllowed = 0,
kDenied
};

// Ranger policy data structure
struct policy_item
{
Expand Down Expand Up @@ -56,8 +91,17 @@ struct acl_policies
deny_policies,
deny_policies_exclude);

// Check whether the 'user_name' is allowed to access the resource by type of 'ac_type'.
bool allowed(const access_type &ac_type, const std::string &user_name) const;
// Check if 'allow_policies' or 'deny_policies' allow or deny 'user_name' to access the resource
// by type 'ac_type'.
policy_check_status policies_check(const access_type &ac_type,
const std::string &user_name,
const policy_check_type &check_type) const;

policy_check_status do_policies_check(const policy_check_type &check_type,
const access_type &ac_type,
const std::string &user_name,
const std::vector<policy_item> &policies,
const std::vector<policy_item> &exclude_policies) const;
};

// A policy data structure definition of ranger resources
Expand All @@ -68,8 +112,92 @@ struct ranger_resource_policy
std::unordered_set<std::string> table_names;
acl_policies policies;

DEFINE_JSON_SERIALIZATION(name, database_names, table_names, policies)
DEFINE_JSON_SERIALIZATION(name, database_names, table_names, policies);
};

// A policy data structure definition of the DATABASE_TABLE resource, which will be set in
// 'app_envs'
struct matched_database_table_policy
{
std::string matched_database_name;
std::string matched_table_name;
acl_policies policies;

DEFINE_JSON_SERIALIZATION(matched_database_name, matched_table_name, policies);
};

// Returns 'access_control_result::kAllowed' if 'policies' allows 'user_name' to access
// 'database_name' via 'ac_type', returns 'access_control_result::kDenied' means not.
// 'need_match_database' being true means that the 'policies' needs to be matched to the database
// first, false means not.
// If 'ac_type' is DATABASE access type, it needs to match database, if 'ac_type' is a GLOBAL access
// type, it does not need to match.
/*
*** Ranger Policy Evaluation Flow ***

+-----------------+
\ Resource access \
\ request \
+-------+---------+
|
+-----v-------+
/ \
/ Has a \
+-----N------+ resource policy <----------------N-----------------+
| \ been matched ? / |
| \ / |
| +-----+-------+ |
| | |
| Y |
| | |
| +-----v-------+ +-------------+ |
| / \ / \ |
| / Has more \ / Has more \ |
| +-----> policies with +---N--+---->+ policies with +-+
| | \ Deny Condition? / | \ Allow Condition?/
| | \ / | \ /
| | +------+------+ | +------+------+
| | | | |
| | Y | Y
| | | | |
| | +------v------+ | +------v------+
| | / Request \ | / Request \
| | / matches a deny \ | /matches an allow \
| +--N--+ condition in the + +--N--+ condition in the +
| | \ policy? / | \ policy? /
| | \ / | \ /
| | +------+------+ | +------+------+
| | | | |
| | Y | Y
| | | | |
| | +------v------+ | +------v------+
| | / Request \ | / Request \
| | / matches a deny \ | / matches an allow\
| +--Y--+ exclude in the + +--Y--+ exclude in the +
| \ policy? / \ policy? /
| \ / \ /
| +------+------+ +------+------+
| | |
| N N
| | |
+-----v-----+ +------v------+ +------v------+
| DENY | | DENY | | ALLOW |
+-----------+ +-------------+ +-------------+
*/
access_control_result
check_ranger_resource_policy_allowed(const std::vector<ranger_resource_policy> &policies,
const access_type &ac_type,
const std::string &user_name,
bool need_match_database,
const std::string &database_name,
const std::string &default_database_name);

// Return 'access_control_result::kAllowed' if 'policies' allow 'user_name' to access, this is used
// for DATABASE_TABLE resource, returns 'access_control_result::kDenied' means not.
access_control_result check_ranger_database_table_policy_allowed(
const std::vector<matched_database_table_policy> &policies,
const access_type &ac_type,
const std::string &user_name);

} // namespace ranger
} // namespace dsn
Loading