Implement OSD-side authorization for operations

This commit is contained in:
Vitaliy Filippov
2026-05-19 17:19:47 +03:00
parent 9db0980fc1
commit b21c92cb7a
25 changed files with 305 additions and 101 deletions
+3 -3
View File
@@ -264,9 +264,9 @@ static const char* help_text =
" Start HTTP server able to handle CLI commands over a REST API. Options:\n"
" --bind_address ADDR Specify server IP address or addresses, separated by space. Default is 127.0.0.1.\n"
" --port 8080 Specify server port.\n"
" --ssl_cert FILE Path to server SSL certificate file (PEM format).\n"
" --ssl_key FILE Path to server SSL private key file.\n"
" --ssl_ca FILE Path to file with SSL CA certificates used to validate client connections.\n"
" --server_cert FILE Path to server TLS certificate file (PEM format).\n"
" --server_key FILE Path to server TLS private key file.\n"
" --client_ca FILE Path to file with TLS CA certificates used to validate client connections.\n"
"\n"
"Use vitastor-cli --help <command> for command details or vitastor-cli --help --all for all details.\n"
"\n"
+2 -7
View File
@@ -27,12 +27,7 @@ struct cli_result_t
json11::Json data;
};
struct cli_user_t
{
std::string name;
std::string type;
std::set<std::string> groups;
};
struct user_info_t;
class cli_tool_t
{
@@ -45,7 +40,7 @@ public:
bool is_command_line = false;
bool color = false;
std::unique_ptr<cli_user_t> user; // for http mode
std::shared_ptr<user_info_t> user; // for http mode
ring_loop_t *ringloop = NULL;
epoll_manager_t *epmgr = NULL;
+1 -1
View File
@@ -9,7 +9,7 @@
bool cli_tool_t::check_image_perm(const inode_config_t & cfg, bool write)
{
return !user ||
user->type == "admin" ||
user->type == user_type_t::ADMIN ||
user->name == cfg.owner ||
cfg.owner_group != "" && user->groups.find(cfg.owner_group) != user->groups.end() ||
!write && cfg.reader_group != "" && user->groups.find(cfg.reader_group) != user->groups.end();
+1 -1
View File
@@ -121,7 +121,7 @@ struct image_creator_t
bool check_pool_permission()
{
if (!parent->user || parent->user->type == "admin")
if (!parent->user || parent->user->type == user_type_t::ADMIN)
{
return true;
}
+1 -1
View File
@@ -147,7 +147,7 @@ resume_1:
inode_t inode_num = INODE_WITH_POOL(pool_id, only_inode_num);
uint64_t used_size = kv.value["raw_used"].uint64_value();
auto stat_it = stats.find(inode_num);
if (parent->user && parent->user->type != "admin" && stat_it == stats.end())
if (parent->user && parent->user->type != user_type_t::ADMIN && stat_it == stats.end())
{
continue;
}
+33 -25
View File
@@ -76,6 +76,7 @@ struct cli_serve_t
int port = 0;
int listen_backlog = 0;
bool ssl = false;
bool use_auth = false;
std::vector<int> listen_fds;
http_context_t *http_ctx = NULL;
std::set<cli_serve_conn_t*> connections;
@@ -111,19 +112,34 @@ struct cli_serve_t
listen_backlog = options["listen_backlog"].uint64_value();
if (!listen_backlog)
listen_backlog = 128;
ssl = json_is_true(options["ssl"]);
if (ssl)
{
std::string ssl_cert = options["ssl_cert"].string_value();
std::string ssl_key = options["ssl_key"].string_value();
std::string ssl_ca = options["ssl_ca"].string_value();
std::string error;
http_ctx = http_context_init(parent->epmgr->tfd, ssl_cert, ssl_key, ssl_ca, ssl_ca != "", error);
if (error != "")
std::string tls_cert = (parent->cli->config.find("server_cert") != parent->cli->config.end()
? parent->cli->config["server_cert"].string_value() : "");
std::string tls_key = (parent->cli->config.find("server_key") != parent->cli->config.end()
? parent->cli->config["server_key"].string_value() : "");
std::string tls_ca = (parent->cli->config.find("client_ca") != parent->cli->config.end()
? parent->cli->config["client_ca"].string_value() : "");
if (tls_cert != "" || tls_key != "" || tls_ca != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
ssl = true;
if (tls_cert == "" || tls_key == "")
{
result = (cli_result_t){ .err = EINVAL, .text = "server_cert and server_key are required to serve HTTPS" };
state = 100;
return;
}
// use_auth is enabled by default when client_ca is set
use_auth = (parent->cli->config["use_auth"].is_null()
? (tls_ca != "")
: json_is_true(parent->cli->config["use_auth"]));
std::string error;
http_ctx = http_context_init(parent->epmgr->tfd, tls_cert, tls_key, tls_ca, tls_ca != "", error);
if (error != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
}
}
}
for (auto & bind_address: bind_addresses)
@@ -344,17 +360,9 @@ struct cli_serve_t
conn->request_path = std::move(req_line[1]);
conn->request_body = std::move(msg->body);
conn->response_type = "";
if (parent->cli->st_cli.use_auth)
if (use_auth)
{
auto user = std::make_unique<cli_user_t>();
user->name = msg->headers["_tls_common_name"];
auto user_it = parent->cli->st_cli.user_info.find(user->name);
auto userinfo = user_it == parent->cli->st_cli.user_info.end() ? user_it->second : json11::Json();
user->type = user->name == "root" ? "admin" : userinfo["type"].string_value();
for (auto & gr: userinfo["groups"].array_items())
{
user->groups.insert(gr.string_value());
}
conn->p->user = parent->cli->st_cli.get_user(msg->headers["_tls_common_name"]);
}
auto ctype = msg->headers["content-type"];
if (conn->request_method != "GET" && conn->request_method != "POST")
@@ -383,10 +391,10 @@ struct cli_serve_t
{
conn->response_type = "application/json";
conn->result = { .text = openapi_description };
if (parent->cli->st_cli.use_auth)
if (use_auth)
{
// Filter available paths by privileges
if (conn->p->user->type == "client")
if (conn->p->user->type == user_type_t::CLIENT)
{
std::string error;
auto openapi = json11::Json::parse(openapi_description, error).object_items();
@@ -403,7 +411,7 @@ struct cli_serve_t
conn->response_type = "application/json";
conn->result = { .text = json11::Json(openapi).dump() };
}
else if (conn->p->user->type != "admin")
else if (conn->p->user->type != user_type_t::ADMIN)
{
conn->response_type = "";
conn->result = { .err = EACCES, .text = "Access denied" };
@@ -418,7 +426,7 @@ struct cli_serve_t
{
conn->result = { .err = ENOSYS, .text = "method /"+uri[0]+" only allows POST requests" };
}
else if (parent->cli->st_cli.use_auth && conn->p->user->type == "client" && !cmd_it->second.allow_client)
else if (use_auth && conn->p->user->type == user_type_t::CLIENT && !cmd_it->second.allow_client)
{
conn->result = { .err = EACCES, .text = "Access denied" };
}