Support TLS CN authentication and per-image permissions in vitastor-cli serve
This commit is contained in:
@@ -16,6 +16,7 @@ const etcd_allow = new RegExp('^'+[
|
|||||||
'config/pools',
|
'config/pools',
|
||||||
'config/osd/[1-9]\\d*',
|
'config/osd/[1-9]\\d*',
|
||||||
'config/pgs', // old name
|
'config/pgs', // old name
|
||||||
|
'config/user/.*',
|
||||||
'pg/config',
|
'pg/config',
|
||||||
'config/inode/[1-9]\\d*/[1-9]\\d*',
|
'config/inode/[1-9]\\d*/[1-9]\\d*',
|
||||||
'osd/state/[1-9]\\d*',
|
'osd/state/[1-9]\\d*',
|
||||||
@@ -234,6 +235,13 @@ const etcd_tree = {
|
|||||||
}
|
}
|
||||||
}, */
|
}, */
|
||||||
inode: {},
|
inode: {},
|
||||||
|
/* user: {
|
||||||
|
<username>: {
|
||||||
|
type: 'osd'|'mon'|'admin'|'client',
|
||||||
|
groups: string[],
|
||||||
|
},
|
||||||
|
}, */
|
||||||
|
user: {},
|
||||||
},
|
},
|
||||||
osd: {
|
osd: {
|
||||||
state: {
|
state: {
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
|||||||
}
|
}
|
||||||
this->etcd_ca = config["etcd_ca"].string_value();
|
this->etcd_ca = config["etcd_ca"].string_value();
|
||||||
this->etcd_prefix = config["etcd_prefix"].string_value();
|
this->etcd_prefix = config["etcd_prefix"].string_value();
|
||||||
|
this->use_auth = config["use_auth"].bool_value();
|
||||||
if (this->etcd_prefix == "")
|
if (this->etcd_prefix == "")
|
||||||
{
|
{
|
||||||
this->etcd_prefix = "/vitastor";
|
this->etcd_prefix = "/vitastor";
|
||||||
@@ -1353,6 +1354,14 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
|||||||
if (on_change_node_placement_hook)
|
if (on_change_node_placement_hook)
|
||||||
on_change_node_placement_hook();
|
on_change_node_placement_hook();
|
||||||
}
|
}
|
||||||
|
else if (use_auth && key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
|
||||||
|
{
|
||||||
|
// <etcd_prefix>/config/user/<username>
|
||||||
|
if (!value.is_object())
|
||||||
|
user_info.erase(key.substr(etcd_prefix.length()+13));
|
||||||
|
else
|
||||||
|
user_info[key.substr(etcd_prefix.length()+13)] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
|
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ public:
|
|||||||
int etcd_slow_timeout = 5000;
|
int etcd_slow_timeout = 5000;
|
||||||
int etcd_min_reload_interval = 1000;
|
int etcd_min_reload_interval = 1000;
|
||||||
bool infinite_start = true;
|
bool infinite_start = true;
|
||||||
|
bool use_auth = false;
|
||||||
uint64_t global_block_size = DEFAULT_BLOCK_SIZE;
|
uint64_t global_block_size = DEFAULT_BLOCK_SIZE;
|
||||||
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
|
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
|
||||||
uint32_t global_immediate_commit = IMMEDIATE_NONE;
|
uint32_t global_immediate_commit = IMMEDIATE_NONE;
|
||||||
@@ -170,6 +171,7 @@ public:
|
|||||||
std::set<osd_num_t> seen_peers;
|
std::set<osd_num_t> seen_peers;
|
||||||
std::map<inode_t, inode_config_t> inode_config;
|
std::map<inode_t, inode_config_t> inode_config;
|
||||||
std::map<std::string, inode_t> inode_by_name;
|
std::map<std::string, inode_t> inode_by_name;
|
||||||
|
std::map<std::string, json11::Json> user_info;
|
||||||
json11::Json node_placement;
|
json11::Json node_placement;
|
||||||
|
|
||||||
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
|
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
|
||||||
|
|||||||
+19
-10
@@ -187,6 +187,19 @@ bool openssl_ctx_use_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem)
|
|||||||
: !!SSL_CTX_load_verify_locations(ssl_ctx, file_or_pem.c_str(), NULL);
|
: !!SSL_CTX_load_verify_locations(ssl_ctx, file_or_pem.c_str(), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string openssl_get_cn(X509 *x509)
|
||||||
|
{
|
||||||
|
X509_NAME* subj = X509_get_subject_name(x509);
|
||||||
|
int pos = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
|
||||||
|
if (pos != -1)
|
||||||
|
{
|
||||||
|
X509_NAME_ENTRY* cn = X509_NAME_get_entry(subj, pos);
|
||||||
|
ASN1_STRING* str = X509_NAME_ENTRY_get_data(cn);
|
||||||
|
return std::string((const char*)ASN1_STRING_get0_data(str), ASN1_STRING_length(str));
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem, std::string & common_name)
|
bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem, std::string & common_name)
|
||||||
{
|
{
|
||||||
BIO *bio = NULL;
|
BIO *bio = NULL;
|
||||||
@@ -208,16 +221,7 @@ bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem, std
|
|||||||
{
|
{
|
||||||
ok = SSL_CTX_use_certificate(ssl_ctx, x509);
|
ok = SSL_CTX_use_certificate(ssl_ctx, x509);
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
common_name = openssl_get_cn(x509);
|
||||||
X509_NAME* subj = X509_get_subject_name(x509);
|
|
||||||
int pos = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
|
||||||
X509_NAME_ENTRY* cn = X509_NAME_get_entry(subj, pos);
|
|
||||||
ASN1_STRING* str = X509_NAME_ENTRY_get_data(cn);
|
|
||||||
common_name = std::string((const char*)ASN1_STRING_get0_data(str), ASN1_STRING_length(str));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
X509_free(x509);
|
X509_free(x509);
|
||||||
}
|
}
|
||||||
BIO_free(bio);
|
BIO_free(bio);
|
||||||
@@ -1136,6 +1140,11 @@ bool http_co_t::handle_read()
|
|||||||
}
|
}
|
||||||
state = HTTP_CO_REQ_HDR_RECEIVED;
|
state = HTTP_CO_REQ_HDR_RECEIVED;
|
||||||
parse_http_headers(response, &parsed, true);
|
parse_http_headers(response, &parsed, true);
|
||||||
|
if (ssl)
|
||||||
|
{
|
||||||
|
auto x509 = SSL_get0_peer_certificate(ssl_cli);
|
||||||
|
parsed.headers["_tls_common_name"] = openssl_get_cn(x509);
|
||||||
|
}
|
||||||
auto conn_it = parsed.headers.find("connection");
|
auto conn_it = parsed.headers.find("connection");
|
||||||
keepalive = (conn_it != parsed.headers.end() && conn_it->second == "keep-alive");
|
keepalive = (conn_it != parsed.headers.end() && conn_it->second == "keep-alive");
|
||||||
auto enc_it = parsed.headers.find("transfer-encoding");
|
auto enc_it = parsed.headers.find("transfer-encoding");
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "object_id.h"
|
#include "object_id.h"
|
||||||
#include "ringloop.h"
|
#include "ringloop.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
struct rm_inode_t;
|
struct rm_inode_t;
|
||||||
struct snap_merger_t;
|
struct snap_merger_t;
|
||||||
@@ -26,6 +27,13 @@ struct cli_result_t
|
|||||||
json11::Json data;
|
json11::Json data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct cli_user_t
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string type;
|
||||||
|
std::set<std::string> groups;
|
||||||
|
};
|
||||||
|
|
||||||
class cli_tool_t
|
class cli_tool_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -37,6 +45,8 @@ public:
|
|||||||
bool is_command_line = false;
|
bool is_command_line = false;
|
||||||
bool color = false;
|
bool color = false;
|
||||||
|
|
||||||
|
std::unique_ptr<cli_user_t> user; // for http mode
|
||||||
|
|
||||||
ring_loop_t *ringloop = NULL;
|
ring_loop_t *ringloop = NULL;
|
||||||
epoll_manager_t *epmgr = NULL;
|
epoll_manager_t *epmgr = NULL;
|
||||||
cluster_client_t *cli = NULL;
|
cluster_client_t *cli = NULL;
|
||||||
@@ -53,6 +63,8 @@ public:
|
|||||||
void change_parent(inode_t cur, inode_t new_parent, cli_result_t *result);
|
void change_parent(inode_t cur, inode_t new_parent, cli_result_t *result);
|
||||||
inode_config_t* get_inode_cfg(const std::string & name);
|
inode_config_t* get_inode_cfg(const std::string & name);
|
||||||
|
|
||||||
|
bool check_image_perm(const inode_config_t & cfg, bool write);
|
||||||
|
|
||||||
friend struct rm_inode_t;
|
friend struct rm_inode_t;
|
||||||
friend struct snap_merger_t;
|
friend struct snap_merger_t;
|
||||||
friend struct snap_flattener_t;
|
friend struct snap_flattener_t;
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
#include "cluster_client.h"
|
#include "cluster_client.h"
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
|
|
||||||
|
bool cli_tool_t::check_image_perm(const inode_config_t & cfg, bool write)
|
||||||
|
{
|
||||||
|
return !user ||
|
||||||
|
user->type == "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();
|
||||||
|
}
|
||||||
|
|
||||||
json11::Json::object cli_tool_t::format_image(const inode_config_t & cfg)
|
json11::Json::object cli_tool_t::format_image(const inode_config_t & cfg)
|
||||||
{
|
{
|
||||||
auto pool_it = cli->st_cli.pool_config.find(INODE_POOL(cfg.num));
|
auto pool_it = cli->st_cli.pool_config.find(INODE_POOL(cfg.num));
|
||||||
@@ -20,6 +29,18 @@ json11::Json::object cli_tool_t::format_image(const inode_config_t & cfg)
|
|||||||
{ "readonly", cfg.readonly },
|
{ "readonly", cfg.readonly },
|
||||||
{ "deleted", cfg.deleted },
|
{ "deleted", cfg.deleted },
|
||||||
};
|
};
|
||||||
|
if (cfg.owner != "")
|
||||||
|
{
|
||||||
|
img["owner"] = cfg.owner;
|
||||||
|
}
|
||||||
|
if (cfg.owner_group != "")
|
||||||
|
{
|
||||||
|
img["owner_group"] = cfg.owner_group;
|
||||||
|
}
|
||||||
|
if (cfg.reader_group != "")
|
||||||
|
{
|
||||||
|
img["reader_group"] = cfg.reader_group;
|
||||||
|
}
|
||||||
if (!cfg.enc_key.empty())
|
if (!cfg.enc_key.empty())
|
||||||
{
|
{
|
||||||
img["encrypted"] = true;
|
img["encrypted"] = true;
|
||||||
|
|||||||
@@ -121,6 +121,23 @@ struct image_creator_t
|
|||||||
create_snapshot();
|
create_snapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool check_pool_permission()
|
||||||
|
{
|
||||||
|
if (!parent->user || parent->user->type == "admin")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
auto pool_it = parent->cli->st_cli.pool_config.find(new_pool_id);
|
||||||
|
if (pool_it == parent->cli->st_cli.pool_config.end() ||
|
||||||
|
(pool_it->second.creator_group == "" || parent->user->groups.find(pool_it->second.creator_group) == parent->user->groups.end()))
|
||||||
|
{
|
||||||
|
result = (cli_result_t){ .err = EACCES, .text = "Pool image create permission denied" };
|
||||||
|
state = 100;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void create_image()
|
void create_image()
|
||||||
{
|
{
|
||||||
if (state == 2)
|
if (state == 2)
|
||||||
@@ -160,6 +177,10 @@ struct image_creator_t
|
|||||||
state = 100;
|
state = 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!check_pool_permission())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!size && !force_size)
|
if (!size && !force_size)
|
||||||
{
|
{
|
||||||
result = (cli_result_t){ .err = EINVAL, .text = "Image size is missing" };
|
result = (cli_result_t){ .err = EINVAL, .text = "Image size is missing" };
|
||||||
@@ -251,11 +272,22 @@ resume_3:
|
|||||||
state = 100;
|
state = 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!parent->check_image_perm(cur_cfg, true))
|
||||||
|
{
|
||||||
|
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
|
||||||
|
state = 100;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!new_pool_id)
|
if (!new_pool_id)
|
||||||
{
|
{
|
||||||
// Create snapshot in the same pool by default
|
// Create snapshot in the same pool by default
|
||||||
new_pool_id = old_pool_id;
|
new_pool_id = old_pool_id;
|
||||||
}
|
}
|
||||||
|
// Verify pool permissions if the pool is different from the original
|
||||||
|
if (new_pool_id != old_pool_id && !check_pool_permission())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
attempt_create();
|
attempt_create();
|
||||||
state = 4;
|
state = 4;
|
||||||
resume_4:
|
resume_4:
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ struct snap_flattener_t
|
|||||||
state = 100;
|
state = 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!parent->check_image_perm(*target_cfg, true))
|
||||||
|
{
|
||||||
|
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
|
||||||
|
state = 100;
|
||||||
|
return;
|
||||||
|
}
|
||||||
target_id = target_cfg->num;
|
target_id = target_cfg->num;
|
||||||
std::vector<inode_t> chain_list;
|
std::vector<inode_t> chain_list;
|
||||||
inode_config_t *cur = target_cfg;
|
inode_config_t *cur = target_cfg;
|
||||||
|
|||||||
+15
-4
@@ -53,13 +53,19 @@ struct image_lister_t
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto & ic: parent->cli->st_cli.inode_config)
|
auto begin_it = list_pool_id
|
||||||
|
? parent->cli->st_cli.inode_config.lower_bound(INODE_WITH_POOL(list_pool_id, 0))
|
||||||
|
: parent->cli->st_cli.inode_config.begin();
|
||||||
|
auto end_it = list_pool_id
|
||||||
|
? parent->cli->st_cli.inode_config.lower_bound(INODE_WITH_POOL(list_pool_id+1, 0))
|
||||||
|
: parent->cli->st_cli.inode_config.end();
|
||||||
|
for (auto it = begin_it; it != end_it; it++)
|
||||||
{
|
{
|
||||||
if (list_pool_id && INODE_POOL(ic.second.num) != list_pool_id)
|
if (!parent->check_image_perm(it->second, false))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
stats[ic.second.num] = parent->format_image(ic.second);
|
stats[it->second.num] = parent->format_image(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +112,7 @@ resume_1:
|
|||||||
state = 100;
|
state = 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// FIXME: Do not always read everything
|
||||||
space_info = parent->etcd_result;
|
space_info = parent->etcd_result;
|
||||||
std::map<pool_id_t, uint64_t> pool_pg_real_size;
|
std::map<pool_id_t, uint64_t> pool_pg_real_size;
|
||||||
for (auto & kv_item: space_info["responses"][0]["response_range"]["kvs"].array_items())
|
for (auto & kv_item: space_info["responses"][0]["response_range"]["kvs"].array_items())
|
||||||
@@ -139,6 +146,11 @@ resume_1:
|
|||||||
}
|
}
|
||||||
inode_t inode_num = INODE_WITH_POOL(pool_id, only_inode_num);
|
inode_t inode_num = INODE_WITH_POOL(pool_id, only_inode_num);
|
||||||
uint64_t used_size = kv.value["raw_used"].uint64_value();
|
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())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// save stats
|
// save stats
|
||||||
auto pool_it = parent->cli->st_cli.pool_config.find(pool_id);
|
auto pool_it = parent->cli->st_cli.pool_config.find(pool_id);
|
||||||
if (pool_it != parent->cli->st_cli.pool_config.end())
|
if (pool_it != parent->cli->st_cli.pool_config.end())
|
||||||
@@ -147,7 +159,6 @@ resume_1:
|
|||||||
used_size = used_size / (pool_pg_real_size[pool_id] ? pool_pg_real_size[pool_id] : 1)
|
used_size = used_size / (pool_pg_real_size[pool_id] ? pool_pg_real_size[pool_id] : 1)
|
||||||
* (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
* (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||||
}
|
}
|
||||||
auto stat_it = stats.find(inode_num);
|
|
||||||
if (stat_it == stats.end())
|
if (stat_it == stats.end())
|
||||||
{
|
{
|
||||||
stats[inode_num] = json11::Json::object {
|
stats[inode_num] = json11::Json::object {
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ struct image_changer_t
|
|||||||
state = 100;
|
state = 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!parent->check_image_perm(cfg, true))
|
||||||
|
{
|
||||||
|
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
|
||||||
|
state = 100;
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (auto & ic: parent->cli->st_cli.inode_config)
|
for (auto & ic: parent->cli->st_cli.inode_config)
|
||||||
{
|
{
|
||||||
if (ic.second.parent_id == inode_num)
|
if (ic.second.parent_id == inode_num)
|
||||||
|
|||||||
@@ -269,10 +269,17 @@ resume_100:
|
|||||||
{
|
{
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
snprintf(buf, 1024, "Parent inode of layer %s (id 0x%jx) not found", cur->name.c_str(), cur->parent_id);
|
snprintf(buf, 1024, "Parent inode of layer %s (id 0x%jx) not found", cur->name.c_str(), cur->parent_id);
|
||||||
|
result = (cli_result_t){ .err = ENOENT, .text = buf };
|
||||||
state = 100;
|
state = 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cur = &it->second;
|
cur = &it->second;
|
||||||
|
if (!parent->check_image_perm(*cur, true))
|
||||||
|
{
|
||||||
|
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
|
||||||
|
state = 100;
|
||||||
|
return;
|
||||||
|
}
|
||||||
chain_list.push_back(cur->num);
|
chain_list.push_back(cur->num);
|
||||||
}
|
}
|
||||||
if (cur->num != from_cfg->num)
|
if (cur->num != from_cfg->num)
|
||||||
@@ -297,6 +304,12 @@ resume_100:
|
|||||||
auto it = sources.find(ic.second.parent_id);
|
auto it = sources.find(ic.second.parent_id);
|
||||||
if (it != sources.end() && sources.find(ic.second.num) == sources.end())
|
if (it != sources.end() && sources.find(ic.second.num) == sources.end())
|
||||||
{
|
{
|
||||||
|
if (!parent->check_image_perm(ic.second, true))
|
||||||
|
{
|
||||||
|
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
|
||||||
|
state = 100;
|
||||||
|
return;
|
||||||
|
}
|
||||||
merge_children.push_back(ic.second.num);
|
merge_children.push_back(ic.second.num);
|
||||||
if (ic.second.readonly || writers_stopped)
|
if (ic.second.readonly || writers_stopped)
|
||||||
{
|
{
|
||||||
|
|||||||
+71
-22
@@ -35,34 +35,35 @@ struct cli_serve_path_t
|
|||||||
{
|
{
|
||||||
std::string cmd;
|
std::string cmd;
|
||||||
bool allow_get;
|
bool allow_get;
|
||||||
|
bool allow_client;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Serve vitastor-cli commands over HTTP in JSON format
|
// Serve vitastor-cli commands over HTTP in JSON format
|
||||||
struct cli_serve_t
|
struct cli_serve_t
|
||||||
{
|
{
|
||||||
std::map<std::string, cli_serve_path_t> cmd_paths = {
|
std::map<std::string, cli_serve_path_t> cmd_paths = {
|
||||||
{"data/delete", {"rm-data", false}},
|
{"data/delete", {"rm-data", false, false}},
|
||||||
{"data/describe", {"describe", true}},
|
{"data/describe", {"describe", true, false}},
|
||||||
{"data/fix", {"fix", false}},
|
{"data/fix", {"fix", false, false}},
|
||||||
{"data/merge", {"merge-data", false}},
|
{"data/merge", {"merge-data", false, false}},
|
||||||
{"image/create", {"create", false}},
|
{"image/create", {"create", false, true}},
|
||||||
{"image/delete", {"rm", false}},
|
{"image/delete", {"rm", false, true}},
|
||||||
{"image/flatten", {"flatten", false}},
|
{"image/flatten", {"flatten", false, true}},
|
||||||
{"image/list", {"ls", true}},
|
{"image/list", {"ls", true, true}},
|
||||||
{"image/modify", {"modify", false}},
|
{"image/modify", {"modify", false, true}},
|
||||||
{"osd/alloc", {"alloc-osd", false}},
|
{"osd/alloc", {"alloc-osd", false, false}},
|
||||||
{"osd/delete", {"rm-osd", false}},
|
{"osd/delete", {"rm-osd", false, false}},
|
||||||
{"osd/list", {"ls-osd", true}},
|
{"osd/list", {"ls-osd", true, false}},
|
||||||
{"osd/modify", {"modify-osd", false}},
|
{"osd/modify", {"modify-osd", false, false}},
|
||||||
{"pg/list", {"ls-pgs", true}},
|
{"pg/list", {"ls-pgs", true, false}},
|
||||||
{"pool/create", {"create-pool", false}},
|
{"pool/create", {"create-pool", false, false}},
|
||||||
{"pool/delete", {"rm-pool", false}},
|
{"pool/delete", {"rm-pool", false, false}},
|
||||||
{"pool/list", {"pools", true}},
|
{"pool/list", {"pools", true, false}},
|
||||||
{"pool/modify", {"modify-pool", false}},
|
{"pool/modify", {"modify-pool", false, false}},
|
||||||
{"user/delete", {"remove-user", false}},
|
{"user/delete", {"remove-user", false, false}},
|
||||||
{"user/list", {"ls-user", false}},
|
{"user/list", {"ls-user", false, false}},
|
||||||
{"user/modify", {"modify-user", false}},
|
{"user/modify", {"modify-user", false, false}},
|
||||||
{"status", {"status", true}},
|
{"status", {"status", true, false}},
|
||||||
};
|
};
|
||||||
|
|
||||||
cli_tool_t *parent = NULL;
|
cli_tool_t *parent = NULL;
|
||||||
@@ -203,6 +204,12 @@ struct cli_serve_t
|
|||||||
if (text)
|
if (text)
|
||||||
*text = "Bad Request";
|
*text = "Bad Request";
|
||||||
}
|
}
|
||||||
|
else if (err == EACCES)
|
||||||
|
{
|
||||||
|
code = 403;
|
||||||
|
if (text)
|
||||||
|
*text = "Forbidden";
|
||||||
|
}
|
||||||
else if (err == EOPNOTSUPP)
|
else if (err == EOPNOTSUPP)
|
||||||
{
|
{
|
||||||
code = 404;
|
code = 404;
|
||||||
@@ -337,6 +344,18 @@ struct cli_serve_t
|
|||||||
conn->request_path = std::move(req_line[1]);
|
conn->request_path = std::move(req_line[1]);
|
||||||
conn->request_body = std::move(msg->body);
|
conn->request_body = std::move(msg->body);
|
||||||
conn->response_type = "";
|
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"];
|
auto ctype = msg->headers["content-type"];
|
||||||
if (conn->request_method != "GET" && conn->request_method != "POST")
|
if (conn->request_method != "GET" && conn->request_method != "POST")
|
||||||
{
|
{
|
||||||
@@ -364,6 +383,32 @@ struct cli_serve_t
|
|||||||
{
|
{
|
||||||
conn->response_type = "application/json";
|
conn->response_type = "application/json";
|
||||||
conn->result = { .text = openapi_description };
|
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())
|
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" };
|
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
|
else
|
||||||
{
|
{
|
||||||
std::string error;
|
std::string error;
|
||||||
|
|||||||
Reference in New Issue
Block a user