Skip to content
This repository has been archived by the owner on Jun 23, 2022. It is now read-only.

feat(security): implement meta server access controller #655

Merged
merged 22 commits into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
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
7 changes: 7 additions & 0 deletions include/dsn/tool-api/network.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ class rpc_session : public ref_counter
void set_negotiation_succeed();
bool is_negotiation_succeed() const;

void set_client_username(const std::string &user_name);
const std::string &get_client_username() const;

public:
///
/// for subclass to implement receiving message
Expand Down Expand Up @@ -328,6 +331,10 @@ class rpc_session : public ref_counter
rpc_client_matcher *_matcher;

std::atomic_int _delay_server_receive_ms;

// _client_username is only valid if it is a server rpc_session.
// it represents the name of the corresponding client
std::string _client_username;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont couple the username into rpc_session. Place it into negotiation or somewhere. rpc_session should not
be responsible for any authorization stuff. Use interceptor or other means.

Copy link
Contributor Author

@levy5307 levy5307 Nov 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok to add a client_username property for rpc_session. It's a stuff of the session itself, but not provided for security. security is just using it.
It will be a little too complex if we put it into negotiation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No authorization/negotiation then no username. The RPC module should be pure network RPC implementation that can be used in every place whether or not the auth enabled. We must insist open-close principle that prevents such modification to add responsibility to a class.

I will take some time thinking about how to dispose of this variable, if you got no idea then.

Copy link
Contributor Author

@levy5307 levy5307 Nov 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but I don't suggest add user_name to negotiation, because in this way we should get the corresponding negotiation for each rpc_session, which will acquire a lock. It will produce low efficiency

};

// --------- inline implementation --------------
Expand Down
1 change: 1 addition & 0 deletions include/dsn/utility/error_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,5 @@ DEFINE_ERR_CODE(ERR_KRB5_INTERNAL)

DEFINE_ERR_CODE(ERR_SASL_INTERNAL)
DEFINE_ERR_CODE(ERR_SASL_INCOMPLETE)
DEFINE_ERR_CODE(ERR_ACL_DENY)
} // namespace dsn
6 changes: 6 additions & 0 deletions include/dsn/utility/strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <vector>
#include <list>
#include <map>
#include <unordered_set>
#include <iostream>

namespace dsn {
Expand All @@ -14,6 +15,11 @@ void split_args(const char *args,
char splitter = ' ',
bool keep_place_holder = false);

void split_args(const char *args,
/*out*/ std::unordered_set<std::string> &sargs,
char splitter = ' ',
bool keep_place_holder = false);

void split_args(const char *args, /*out*/ std::list<std::string> &sargs, char splitter = ' ');

// kv_map sample (when item_splitter = ',' and kv_splitter = ':'):
Expand Down
16 changes: 16 additions & 0 deletions src/meta/meta_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "meta/duplication/meta_duplication_service.h"
#include "meta_split_service.h"
#include "meta_bulk_load_service.h"
#include "runtime/security/access_controller.h"

namespace dsn {
namespace replication {
Expand Down Expand Up @@ -76,6 +77,8 @@ meta_service::meta_service()
"replica server disconnect count in the recent period");
_unalive_nodes_count.init_app_counter(
"eon.meta_service", "unalive_nodes", COUNTER_TYPE_NUMBER, "current count of unalive nodes");

_access_controller = security::create_meta_access_controller();
}

meta_service::~meta_service()
Expand Down Expand Up @@ -120,6 +123,12 @@ int meta_service::check_leader(TRpcHolder rpc, rpc_address *forward_address)
template <typename TRpcHolder>
bool meta_service::check_status(TRpcHolder rpc, rpc_address *forward_address)
{
if (!_access_controller->allowed(rpc.dsn_request())) {
rpc.response().err = ERR_ACL_DENY;
ddebug("reject request with ERR_ACL_DENY");
levy5307 marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

int result = check_leader(rpc, forward_address);
if (result == 0)
return false;
Expand All @@ -141,6 +150,13 @@ bool meta_service::check_status(TRpcHolder rpc, rpc_address *forward_address)
template <typename TRespType>
bool meta_service::check_status_with_msg(message_ex *req, TRespType &response_struct)
{
if (!_access_controller->allowed(req)) {
ddebug("reject request with ERR_ACL_DENY");
response_struct.err = ERR_ACL_DENY;
reply(req, response_struct);
return false;
}

int result = check_leader(req, nullptr);
if (result == 0) {
return false;
Expand Down
5 changes: 5 additions & 0 deletions src/meta/meta_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
#include "block_service/block_service_manager.h"

namespace dsn {
namespace security {
class access_controller;
} // namespace security
namespace replication {

class server_state;
Expand Down Expand Up @@ -261,6 +264,8 @@ class meta_service : public serverlet<meta_service>
perf_counter_wrapper _unalive_nodes_count;

dsn::task_tracker _tracker;

std::unique_ptr<security::access_controller> _access_controller;
};

} // namespace replication
Expand Down
7 changes: 7 additions & 0 deletions src/runtime/rpc/network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,13 @@ bool rpc_session::is_negotiation_succeed() const
}
}

void rpc_session::set_client_username(const std::string &user_name)
{
_client_username = user_name;
}

const std::string &rpc_session::get_client_username() const { return _client_username; }

////////////////////////////////////////////////////////////////////////////////////////////////
network::network(rpc_engine *srv, network *inner_provider)
: _engine(srv), _client_hdr_format(NET_HDR_DSN), _unknown_msg_header_format(NET_HDR_INVALID)
Expand Down
47 changes: 47 additions & 0 deletions src/runtime/security/access_controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "access_controller.h"

#include <dsn/utility/flags.h>
#include <dsn/utility/strings.h>
#include <dsn/utility/smart_pointers.h>
#include "meta_access_controller.h"

namespace dsn {
namespace security {
DSN_DEFINE_bool("security", enable_acl, false, "whether enable access controller or not");
DSN_DEFINE_string("security", super_users, "", "super user for access controller");

access_controller::access_controller() { utils::split_args(FLAGS_super_users, _super_users, ','); }

access_controller::~access_controller() {}
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved

bool access_controller::pre_check(const std::string &user_name)
{
if (!FLAGS_enable_acl || _super_users.find(user_name) != _super_users.end()) {
return true;
}
return false;
}

std::unique_ptr<access_controller> create_meta_access_controller()
{
return make_unique<meta_access_controller>();
}
} // namespace security
} // namespace dsn
54 changes: 54 additions & 0 deletions src/runtime/security/access_controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.

#pragma once

#include <memory>
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
#include <unordered_set>

namespace dsn {
class message_ex;
namespace security {

class access_controller
{
public:
access_controller();
virtual ~access_controller() = 0;

/**
* reset the access controller
* acls - the new acls to reset
**/
virtual void reset(const std::string &acls){};
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved

/**
* check if the message received is allowd to do something.
* msg - the message received
**/
virtual bool allowed(message_ex *msg) = 0;

protected:
bool pre_check(const std::string &user_name);
friend class meta_access_controller_test;

std::unordered_set<std::string> _super_users;
};

std::unique_ptr<access_controller> create_meta_access_controller();
} // namespace security
} // namespace dsn
69 changes: 69 additions & 0 deletions src/runtime/security/meta_access_controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "meta_access_controller.h"

#include <dsn/tool-api/rpc_message.h>
#include <dsn/utility/flags.h>
#include <dsn/tool-api/network.h>
#include <dsn/dist/fmt_logging.h>

namespace dsn {
namespace security {
DSN_DEFINE_string("security",
meta_acl_rpc_allow_list,
"",
"allowed list of rpc codes for meta_access_controller");

meta_access_controller::meta_access_controller()
{
// MetaServer serves the allow-list RPC from all users. RPCs unincluded are accessible to only
// superusers.
if (strlen(FLAGS_meta_acl_rpc_allow_list) == 0) {
register_allowed_list("RPC_CM_LIST_APPS");
register_allowed_list("RPC_CM_LIST_NODES");
register_allowed_list("RPC_CM_CLUSTER_INFO");
register_allowed_list("RPC_CM_QUERY_PARTITION_CONFIG_BY_INDEX");
} else {
std::vector<std::string> rpc_code_white_list;
utils::split_args(FLAGS_meta_acl_rpc_allow_list, rpc_code_white_list, ',');
for (const auto &rpc_code : rpc_code_white_list) {
register_allowed_list(rpc_code);
}
}
}

bool meta_access_controller::allowed(message_ex *msg)
{
if (pre_check(msg->io_session->get_client_username()) ||
_allowed_rpc_code_list.find(msg->rpc_code().code()) != _allowed_rpc_code_list.end()) {
return true;
}
return false;
}

void meta_access_controller::register_allowed_list(const std::string &rpc_code)
{
auto code = task_code::try_get(rpc_code, TASK_CODE_INVALID);
dassert_f(code != TASK_CODE_INVALID,
"invalid task code({}) in rpc_code_white_list of security section",
rpc_code);

_allowed_rpc_code_list.insert(code);
}
} // namespace security
} // namespace dsn
40 changes: 40 additions & 0 deletions src/runtime/security/meta_access_controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.

#pragma once

#include "access_controller.h"

#include <unordered_set>

namespace dsn {
class message_ex;
namespace security {

class meta_access_controller : public access_controller
{
public:
meta_access_controller();
bool allowed(message_ex *msg) override;

private:
void register_allowed_list(const std::string &rpc_code);

std::unordered_set<int> _allowed_rpc_code_list;
};
} // namespace security
} // namespace dsn
21 changes: 21 additions & 0 deletions src/runtime/security/sasl_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "sasl_client_wrapper.h"

#include <sasl/sasl.h>
#include <dsn/utility/fail_point.h>

namespace dsn {
namespace security {
Expand All @@ -39,6 +40,26 @@ sasl_wrapper::~sasl_wrapper()
}
}

error_s sasl_wrapper::retrive_username(std::string &output)
{
FAIL_POINT_INJECT_F("sasl_wrapper_retrive_username", [](dsn::string_view str) {
error_code err = error_code::try_get(str.data(), ERR_UNKNOWN);
return error_s::make(err);
});

// retrive username from _conn.
// If this is a sasl server, it gets the name of the corresponding sasl client.
// But if this is a sasl client, it gets the name of itself
char *username = nullptr;
error_s err_s = wrap_error(sasl_getprop(_conn, SASL_USERNAME, (const void **)&username));
if (err_s.is_ok()) {
output = username;
levy5307 marked this conversation as resolved.
Show resolved Hide resolved
output = output.substr(0, output.find_last_of('@'));
output = output.substr(0, output.find_first_of('/'));
}
return err_s;
}

error_s sasl_wrapper::wrap_error(int sasl_err)
{
error_s ret;
Expand Down
6 changes: 6 additions & 0 deletions src/runtime/security/sasl_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ class sasl_wrapper
virtual error_s init() = 0;
virtual error_s start(const std::string &mechanism, const blob &input, blob &output) = 0;
virtual error_s step(const blob &input, blob &output) = 0;
/**
* retrive username from sasl connection.
* If this is a sasl server, it gets the name of the corresponding sasl client.
* But if this is a sasl client, it gets the name of itself
**/
error_s retrive_username(/*out*/ std::string &output);

protected:
sasl_wrapper() = default;
Expand Down
Loading