From 0053a0d8259372e961458f63ec46b4f57c0c1e4e Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Sun, 22 Mar 2026 19:35:16 +0300 Subject: [PATCH] Add image owner/owner_group/reader_group support (for antietcd VitastorAuthFilter) --- mon/etcd_schema.js | 6 ++++ src/client/etcd_state_client.cpp | 17 +++++++++++ src/client/etcd_state_client.h | 3 ++ src/client/http_client.cpp | 52 +++++++++++++++++++++++--------- src/client/http_client.h | 1 + src/client/msgr_send.cpp | 2 +- src/cmd/cli.cpp | 17 ++++++++--- src/cmd/cli_create.cpp | 18 +++++++++++ src/cmd/cli_modify.cpp | 16 ++++++++++ src/cmd/cli_pool_cfg.cpp | 2 +- src/cmd/openapi.json | 11 +++++-- 11 files changed, 122 insertions(+), 23 deletions(-) diff --git a/mon/etcd_schema.js b/mon/etcd_schema.js index f3ed7caf..ee8b7516 100644 --- a/mon/etcd_schema.js +++ b/mon/etcd_schema.js @@ -208,6 +208,8 @@ const etcd_tree = { primary_affinity_tags?: 'nvme' | [ 'nvme', ... ], // scrub interval scrub_interval?: '30d', + // users allowed to create images in this pool + creator_group?: '', }, ... }, */ @@ -224,6 +226,10 @@ const etcd_tree = { parent_id?: , readonly?: boolean, deleted?: boolean, + enc_key?: string, + owner?: string, + owner_group?: string, + reader_group?: string, } } }, */ diff --git a/src/client/etcd_state_client.cpp b/src/client/etcd_state_client.cpp index 5e32ce14..2b3afcea 100644 --- a/src/client/etcd_state_client.cpp +++ b/src/client/etcd_state_client.cpp @@ -1017,6 +1017,8 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv) pc.used_for_app = "fs:"+pc.used_for_app; else pc.used_for_app = pool_item.second["used_for_app"].as_string(); + // Create group permission + pc.creator_group = pool_item.second["creator_group"].string_value(); // Local Read Configuration std::string local_reads = pool_item.second["local_reads"].string_value(); if (local_reads == "nearest") @@ -1441,6 +1443,18 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf { new_cfg["deleted"] = true; } + if (!cfg->owner.empty()) + { + new_cfg["owner"] = cfg->owner; + } + if (!cfg->owner_group.empty()) + { + new_cfg["owner_group"] = cfg->owner_group; + } + if (!cfg->reader_group.empty()) + { + new_cfg["reader_group"] = cfg->reader_group; + } if (cfg->meta.is_object()) { new_cfg["meta"] = cfg->meta; @@ -1487,6 +1501,9 @@ inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, js .readonly = value["readonly"].bool_value(), .deleted = value["deleted"].bool_value(), .enc_key = std::move(enc_key), + .owner = value["owner"].string_value(), + .owner_group = value["owner_group"].string_value(), + .reader_group = value["reader_group"].string_value(), .meta = value["meta"], .mod_revision = mod_revision, }; diff --git a/src/client/etcd_state_client.h b/src/client/etcd_state_client.h index b0b25418..6b61c1d9 100644 --- a/src/client/etcd_state_client.h +++ b/src/client/etcd_state_client.h @@ -69,6 +69,7 @@ struct pool_config_t std::map pg_config; uint64_t scrub_interval = 0; std::string used_for_app; + std::string creator_group; int backfillfull = 0; int local_reads = 0; @@ -87,6 +88,8 @@ struct inode_config_t bool readonly = false; bool deleted = false; std::string enc_key; + // Permissions + std::string owner, owner_group, reader_group; // Arbitrary metadata json11::Json meta; // Change revision of the metadata in etcd diff --git a/src/client/http_client.cpp b/src/client/http_client.cpp index cf0127f5..9e5ea723 100644 --- a/src/client/http_client.cpp +++ b/src/client/http_client.cpp @@ -48,6 +48,7 @@ struct http_context_t #ifdef WITH_OPENSSL SSL_CTX *ssl_ctx = NULL; + std::string ssl_cn; #endif ~http_context_t() @@ -186,24 +187,41 @@ 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); } -bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem) +bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem, std::string & common_name) { + BIO *bio = NULL; + std::string contents; if (file_or_pem.substr(0, 5) == "-----") + bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size()); + else { - BIO *bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size()); - if (!bio) + contents = read_file(file_or_pem); + if (!contents.size()) return false; - X509 *x509 = PEM_read_bio_X509(bio, NULL, 0, NULL); - bool ok = !!x509; - if (x509) - { - ok = SSL_CTX_use_certificate(ssl_ctx, x509); - X509_free(x509); - } - BIO_free(bio); - return ok; + bio = BIO_new_mem_buf(contents.data(), contents.size()); } - return !!SSL_CTX_use_certificate_file(ssl_ctx, file_or_pem.c_str(), SSL_FILETYPE_PEM); + if (!bio) + return false; + X509 *x509 = PEM_read_bio_X509(bio, NULL, 0, NULL); + bool ok = !!x509; + if (x509) + { + ok = SSL_CTX_use_certificate(ssl_ctx, x509); + if (ok) + { + 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); + } + BIO_free(bio); + return ok; } bool openssl_ctx_use_key(SSL_CTX *ssl_ctx, const std::string & file_or_pem) @@ -243,6 +261,7 @@ http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ss ctx->ssl_key = ssl_key; ctx->ssl_ca = ssl_ca; ctx->ssl_ctx = ssl_ctx; + ctx->ssl_cn = ""; if (!ssl_ctx) goto init_err; SSL_CTX_set_verify(ssl_ctx, verify_peer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); @@ -251,7 +270,7 @@ http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ss if (!openssl_ctx_use_ca(ssl_ctx, ssl_ca)) goto init_err; if (ssl_cert != "" && ssl_key != "" && - (!openssl_ctx_use_cert(ssl_ctx, ssl_cert) || + (!openssl_ctx_use_cert(ssl_ctx, ssl_cert, ctx->ssl_cn) || !openssl_ctx_use_key(ssl_ctx, ssl_key))) goto init_err; #endif @@ -262,6 +281,11 @@ init_err: return NULL; } +std::string http_context_get_ssl_cn(http_context_t *ctx) +{ + return ctx->ssl_cn; +} + struct http_ctx_resolve_t { std::function & addrs)> cb; diff --git a/src/client/http_client.h b/src/client/http_client.h index 72761d0c..fc663698 100644 --- a/src/client/http_client.h +++ b/src/client/http_client.h @@ -48,6 +48,7 @@ struct http_co_t; http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ssl_cert, const std::string & ssl_key, const std::string & ssl_ca, bool verify_peer, std::string & error); +std::string http_context_get_ssl_cn(http_context_t *ctx); void http_resolve(http_context_t *ctx, bool ssl, std::string host, std::function & addrs)> cb); void http_context_destroy(http_context_t *ctx); diff --git a/src/client/msgr_send.cpp b/src/client/msgr_send.cpp index 4ade5e20..a39fd5ed 100644 --- a/src/client/msgr_send.cpp +++ b/src/client/msgr_send.cpp @@ -254,7 +254,7 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t { fprintf(stderr, "Client %ju socket write error: expected to send " "%zu bytes with MSG_WAITALL but sent %u. Disconnecting client\n", cl->client_id, cl->send_list_size, result); - stop_client(cl->peer_fd); + stop_client(cl->client_id); return; } for (auto op: cl->send_free_ops) diff --git a/src/cmd/cli.cpp b/src/cmd/cli.cpp index 9f578d21..48b855b0 100644 --- a/src/cmd/cli.cpp +++ b/src/cmd/cli.cpp @@ -49,6 +49,9 @@ static const char* help_text = " --enc-key random Generate a new random AES-256-XTS encryption key for the new image.\n" " --enc-key HEX Set a specified AES-256-XTS key (64 bytes in hex) for the new image.\n" " --enc-key vault:ID Use an encryption key from an external Vault secret with specified ID.\n" + " --owner username Set owner (default is the current user from TLS certificate).\n" + " --owner_group name Set owner group name.\n" + " --reader_group rdr Set reader group name.\n" "\n" "vitastor-cli create --snapshot [OPTIONS] \n" "vitastor-cli snap-create [OPTIONS] @\n" @@ -63,10 +66,13 @@ static const char* help_text = " Rename, resize image or change its readonly status. Images with children can't be made read-write.\n" " If the new size is smaller than the old size, extra data will be purged.\n" " You should resize file system in the image, if present, before shrinking it.\n" - " --deleted 1|0 Set/clear 'deleted image' flag (set automatically during unfinished deletes).\n" - " -f|--force Proceed with shrinking or setting readwrite flag even if the image has children.\n" - " --down-ok Proceed with shrinking even if some data will be left on unavailable OSDs.\n" - " --enc-key HEX Change image encryption key (allowed only with --force).\n" + " --deleted 1|0 Set/clear 'deleted image' flag (set automatically during unfinished deletes).\n" + " -f|--force Proceed with shrinking or setting readwrite flag even if the image has children.\n" + " --down-ok Proceed with shrinking even if some data will be left on unavailable OSDs.\n" + " --enc-key HEX Change image encryption key (allowed only with --force).\n" + " --owner username Change image owner.\n" + " --owner_group name Change image owner group name.\n" + " --reader_group rdr Change image reader group name.\n" "\n" "vitastor-cli dd [iimg= | if=] [oimg= | of=] [bs=1M]\n" " [count=N] [seek/oseek=N] [skip/iseek=M] [iodepth=N] [status=progress]\n" @@ -205,6 +211,7 @@ static const char* help_text = " --used_for_app s3: Mark pool as used for S3 location with name \n" " --pg_stripe_size Increase object grouping stripe\n" " --max_osd_combinations 10000 Maximum number of random combinations for LP solver input\n" + " --creator_group User group allowed to create images in this pool.\n" " --wait Wait for the new pool to come online\n" " -f|--force Do not check that cluster has enough OSDs to create the pool\n" " Examples:\n" @@ -216,7 +223,7 @@ static const char* help_text = " [-s|--pg_size ] [--pg_minsize ] [-n|--pg_count ]\n" " [--failure_domain ] [--root_node ] [--osd_tags ] [--used_for_app :]\n" " [--max_osd_combinations ] [--primary_affinity_tags ] [--scrub_interval