Support TLS CN authentication and per-image permissions in vitastor-cli serve

This commit is contained in:
Vitaliy Filippov
2026-04-17 13:53:41 +03:00
parent c014d20fca
commit c371b74e12
12 changed files with 214 additions and 36 deletions
+71 -22
View File
@@ -35,34 +35,35 @@ struct cli_serve_path_t
{
std::string cmd;
bool allow_get;
bool allow_client;
};
// Serve vitastor-cli commands over HTTP in JSON format
struct cli_serve_t
{
std::map<std::string, cli_serve_path_t> cmd_paths = {
{"data/delete", {"rm-data", false}},
{"data/describe", {"describe", true}},
{"data/fix", {"fix", false}},
{"data/merge", {"merge-data", false}},
{"image/create", {"create", false}},
{"image/delete", {"rm", false}},
{"image/flatten", {"flatten", false}},
{"image/list", {"ls", true}},
{"image/modify", {"modify", false}},
{"osd/alloc", {"alloc-osd", false}},
{"osd/delete", {"rm-osd", false}},
{"osd/list", {"ls-osd", true}},
{"osd/modify", {"modify-osd", false}},
{"pg/list", {"ls-pgs", true}},
{"pool/create", {"create-pool", false}},
{"pool/delete", {"rm-pool", false}},
{"pool/list", {"pools", true}},
{"pool/modify", {"modify-pool", false}},
{"user/delete", {"remove-user", false}},
{"user/list", {"ls-user", false}},
{"user/modify", {"modify-user", false}},
{"status", {"status", true}},
{"data/delete", {"rm-data", false, false}},
{"data/describe", {"describe", true, false}},
{"data/fix", {"fix", false, false}},
{"data/merge", {"merge-data", false, false}},
{"image/create", {"create", false, true}},
{"image/delete", {"rm", false, true}},
{"image/flatten", {"flatten", false, true}},
{"image/list", {"ls", true, true}},
{"image/modify", {"modify", false, true}},
{"osd/alloc", {"alloc-osd", false, false}},
{"osd/delete", {"rm-osd", false, false}},
{"osd/list", {"ls-osd", true, false}},
{"osd/modify", {"modify-osd", false, false}},
{"pg/list", {"ls-pgs", true, false}},
{"pool/create", {"create-pool", false, false}},
{"pool/delete", {"rm-pool", false, false}},
{"pool/list", {"pools", true, false}},
{"pool/modify", {"modify-pool", false, false}},
{"user/delete", {"remove-user", false, false}},
{"user/list", {"ls-user", false, false}},
{"user/modify", {"modify-user", false, false}},
{"status", {"status", true, false}},
};
cli_tool_t *parent = NULL;
@@ -203,6 +204,12 @@ struct cli_serve_t
if (text)
*text = "Bad Request";
}
else if (err == EACCES)
{
code = 403;
if (text)
*text = "Forbidden";
}
else if (err == EOPNOTSUPP)
{
code = 404;
@@ -337,6 +344,18 @@ 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)
{
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());
}
}
auto ctype = msg->headers["content-type"];
if (conn->request_method != "GET" && conn->request_method != "POST")
{
@@ -364,6 +383,32 @@ struct cli_serve_t
{
conn->response_type = "application/json";
conn->result = { .text = openapi_description };
if (parent->cli->st_cli.use_auth)
{
// Filter available paths by privileges
if (conn->p->user->type == "client")
{
std::string error;
auto openapi = json11::Json::parse(openapi_description, error).object_items();
json11::Json::object paths;
for (auto & kv: openapi["paths"].object_items())
{
auto cmd_it = cmd_paths.find(kv.first.substr(1));
if (cmd_it != cmd_paths.end() && cmd_it->second.allow_client)
{
paths[kv.first] = kv.second;
}
}
openapi["paths"] = paths;
conn->response_type = "application/json";
conn->result = { .text = json11::Json(openapi).dump() };
}
else if (conn->p->user->type != "admin")
{
conn->response_type = "";
conn->result = { .err = EACCES, .text = "Access denied" };
}
}
}
else if (cmd_it == cmd_paths.end())
{
@@ -373,6 +418,10 @@ 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)
{
conn->result = { .err = EACCES, .text = "Access denied" };
}
else
{
std::string error;