From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id AC1311FF16F for ; Tue, 16 Sep 2025 19:20:37 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B1D7C19AEB; Tue, 16 Sep 2025 19:20:47 +0200 (CEST) From: "Max R. Carrara" To: pve-devel@lists.proxmox.com Date: Tue, 16 Sep 2025 19:20:04 +0200 Message-ID: <20250916172012.739807-4-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20250916172012.739807-1-m.carrara@proxmox.com> References: <20250916172012.739807-1-m.carrara@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1758043235271 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.063 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_1 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH pve-manager master v1 3/6] fix #6816: bin: add pve-ceph-keyring helper and call it in postinst X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" ... in order to update the configuration for the 'client.exporter' Ceph auth entity, its keyring file and the corresponding config section in /etc/pve/ceph.conf for existing Ceph installations. The `pve-ceph-keyring` helper is a more generic version of the `pve-init-ceph-crash` script. The core logic [0] is retained, while the code organization and (error) messages are improved a little. Only the custom 'client.exporter' Ceph auth entity is supported at the moment. Other auth entities can be added declaratively. The helper is intentionally designed to be a little more open to changes in case additional keyring files need to be added in the future. The main motivation for adding this helper is to avoid duplicating any logic for additional Ceph auth entities and keyring files, especially because the main purpose of this helper is to be called in `debian/postinst`. [0]: https://lore.proxmox.com/pve-devel/20240402145523.683008-11-m.carrara@proxmox.com/ Fixes: #6816 Signed-off-by: Max R. Carrara --- PVE/Ceph/Tools.pm | 1 + bin/Makefile | 1 + bin/pve-ceph-keyring | 282 +++++++++++++++++++++++++++++++++++++++++++ debian/postinst | 24 ++++ 4 files changed, 308 insertions(+) create mode 100755 bin/pve-ceph-keyring diff --git a/PVE/Ceph/Tools.pm b/PVE/Ceph/Tools.pm index 8ddce759..afff1a0a 100644 --- a/PVE/Ceph/Tools.pm +++ b/PVE/Ceph/Tools.pm @@ -570,6 +570,7 @@ sub create_or_update_crash_keyring_file { return 0; } +# is also used in `pve-ceph-keyring` helper sub create_or_update_exporter_keyring_file { my ($rados) = @_; diff --git a/bin/Makefile b/bin/Makefile index 777e6759..dd4c53b5 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -31,6 +31,7 @@ SCRIPTS = \ HELPERS = \ pve-startall-delay \ pve-init-ceph-crash \ + pve-ceph-keyring \ pve-firewall-commit \ pve-sdn-commit diff --git a/bin/pve-ceph-keyring b/bin/pve-ceph-keyring new file mode 100755 index 00000000..9d6e89d4 --- /dev/null +++ b/bin/pve-ceph-keyring @@ -0,0 +1,282 @@ +#!/usr/bin/perl + +use v5.36; + +use Getopt::Long qw(GetOptions); +use List::Util qw(first max); + +use PVE::Ceph::Tools; +use PVE::Cluster; +use PVE::RADOS; +use PVE::RPCEnvironment; + +my $CEPH_CFG_FILE = 'ceph.conf'; +my $CEPH_CFG_FILE_PATH = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath'); +my $KEYRING_PATH = '/etc/pve/ceph/$cluster.$name.keyring'; + +my $SUPPORTED_ENTITIES = { + 'client.exporter' => { + 'keyring-func' => \&PVE::Ceph::Tools::create_or_update_exporter_keyring_file, + 'keyring-path' => PVE::Ceph::Tools::get_config('pve_ceph_exporter_key_path'), + }, +}; + +my sub parse_cli_opts : prototype() () { + my $init = ''; + my $needs_help = ''; + + GetOptions( + 'init!' => \$init, + 'help|h!' => \$needs_help, + ); + + return { + init => $init, + 'needs-help' => $needs_help, + entities => [@ARGV], + }; +} + +my sub print_usage : prototype() () { + my @cmd_path_elements = split('/', $0); + my $cmd = $cmd_path_elements[-1] || $0; + + my $supported_entities = join("\n ", sort keys $SUPPORTED_ENTITIES->%*); + + my $actions = [ + ["--init", "Initialize or update Ceph auth entities and keyrings"], + ]; + + my $opts_misc = [ + ["--help, -h", "Print help"], + ]; + + my $ljust = max map { length($_->[0]) } ($actions->@*, $opts_misc->@*); + + my $format_opts = sub ($opt_pairs) { + my @opts_formatted = map { sprintf('%-*s %s', $ljust, $_->[0], $_->[1]) } $opt_pairs->@*; + return join("\n ", @opts_formatted); + }; + + my $actions_str = $format_opts->($actions); + my $opts_misc_str = $format_opts->($opts_misc); + + my $usage = <<~EOF; + $cmd - Ceph authentication entity and keyring helper + + USAGE: + $cmd [OPTIONS] + + SUPPORTED ENTITIES: + $supported_entities + + ACTIONS: + $actions_str + + OTHER OPTIONS: + $opts_misc_str + EOF + + print STDERR $usage; + + return; +} + +my sub print_usage_short : prototype() () { + my @cmd_path_elements = split('/', $0); + my $cmd = $cmd_path_elements[-1] || $0; + + my $usage = <<~EOF; + USAGE: + $cmd [OPTIONS] + + Use -h or --help for more details. + EOF + + print STDERR $usage; + + return; +} + +my sub try_adapt_cfg : prototype($$) ($cfg, $entity) { + my $removed_key = 0; + + print("$entity: Checking configuration.\n"); + + my $add_keyring = sub () { + print("$entity: Setting keyring path to '$KEYRING_PATH'.\n"); + $cfg->{$entity}->{keyring} = $KEYRING_PATH; + return; + }; + + if (!exists($cfg->{$entity})) { + print("$entity: Adding missing section.\n"); + $add_keyring->(); + return 1; + } + + if (exists($cfg->{$entity}->{key})) { + print("$entity: Removing existing usage of key.\n"); + delete($cfg->{$entity}->{key}); + $removed_key = 1; + } + + if (!exists($cfg->{$entity}->{keyring})) { + print("$entity: Keyring path is missing from configuration.\n"); + $add_keyring->(); + return 1; + } + + my $current_keyring_value = $cfg->{$entity}->{keyring}; + if ($current_keyring_value ne $KEYRING_PATH) { + print("$entity: Current keyring path differs from expected path.\n"); + $add_keyring->(); + return 1; + } + + return $removed_key; +} + +my sub update_cluster_cfg : prototype($$$) ($cfg, $entities, $rados) { + if (!defined($rados)) { + my $has_mon_host = defined($cfg->{global}) && defined($cfg->{global}->{mon_host}); + + if ($has_mon_host && $cfg->{global}->{mon_host} ne '') { + die "Connection to RADOS failed even though a monitor is configured.\n" + . "Please verify whether your configuration in '$CEPH_CFG_FILE' is correct.\n"; + } + + print("Connection to RADOS failed and no monitor is configured in '$CEPH_CFG_FILE'.\n" + . "Assuming that things are fine. No action required.\n"); + return; + } + + my @changed_entities; + + for my $entity ($entities->@*) { + my ($keyring_func, $keyring_path) = + $SUPPORTED_ENTITIES->{$entity}->@{qw(keyring-func keyring-path)}; + + my $has_updated_keyring = eval { $keyring_func->() }; + die "Failed to configure keyring for Ceph auth entity '$entity': $@" + if $@; + + print("$entity: Keyring file '$keyring_path' was updated.\n") + if $has_updated_keyring; + + my $changed = try_adapt_cfg($cfg, $entity); + push(@changed_entities, $entity) if $changed; + } + + return @changed_entities; +} + +sub main : prototype() () { + my $opts = parse_cli_opts(); + + if ($opts->{'needs-help'}) { + print_usage(); + exit 0; + } + + if (!scalar($opts->{entities}->@*)) { + print STDERR "Error: No Ceph auth entities specified.\n\n"; + print_usage_short(); + + exit 1; + } + + my @unknown_entities = grep { !exists($SUPPORTED_ENTITIES->{$_}) } $opts->{entities}->@*; + + if (scalar(@unknown_entities)) { + my $err_entities = join(', ', @unknown_entities); + + print STDERR "Error: Unknown entities: $err_entities\n\n"; + print_usage_short(); + + exit 1; + } + + if (!$opts->{init}) { + print STDERR "Error: No action specified.\n\n"; + print_usage_short(); + + exit 1; + } + + # PVE::RADOS expects an active RPC Environment because it forks itself + # and may want to clean up after + my $rpcenv = PVE::RPCEnvironment->setup_default_cli_env(); + + if (!PVE::Ceph::Tools::check_ceph_installed('ceph_bin', 1)) { + print("Ceph is not installed. No action required.\n"); + exit 0; + } + + my $ceph_cfg_path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath'); + if (PVE::Ceph::Tools::check_ceph_installed('ceph_mon', 1) && -f $ceph_cfg_path) { + my $pve_ceph_cfgdir = PVE::Ceph::Tools::get_config('pve_ceph_cfgdir'); + if (!-d $pve_ceph_cfgdir) { + File::Path::make_path($pve_ceph_cfgdir); + } + } + + eval { PVE::Ceph::Tools::check_ceph_inited(); }; + if ($@) { + print("Ceph is not initialized. No action required.\n"); + exit 0; + } + + my $rados = eval { PVE::RADOS->new() }; + my $inner_err = ''; + + my $rval = PVE::Cluster::cfs_lock_file( + $CEPH_CFG_FILE, + undef, + sub { + eval { + my $cfg = PVE::Cluster::cfs_read_file($CEPH_CFG_FILE); + + my @changed_entities = update_cluster_cfg($cfg, $opts->{entities}, $rados); + + if (scalar(@changed_entities)) { + print("Committing updated configuration to '$CEPH_CFG_FILE'.\n"); + PVE::Cluster::cfs_write_file($CEPH_CFG_FILE, $cfg); + print( + "Successfully updated configuration for the following Ceph auth entities: ", + join(', ', @changed_entities), + "\n", + ); + } else { + print( + "Configuration in '$CEPH_CFG_FILE_PATH' does not need to be updated.\n" + ); + } + }; + $inner_err = $@; + + return 1; + }, + ); + + # cfs_lock_file sets $@ explicitly to undef + my $err = $@ // ''; + + my $has_err = !defined($rval) || $inner_err || $err; + + if ($has_err) { + $err =~ s/\n*$//; + $inner_err =~ s/\n*$//; + + if (!defined($rval)) { + warn("Error while acquiring or releasing lock for '$CEPH_CFG_FILE_PATH'.\n"); + warn("Error: $err\n") if $err ne ''; + } + + warn("Error: $inner_err\n") if $inner_err ne ''; + + exit 1; + } +} + +main(); diff --git a/debian/postinst b/debian/postinst index b6e07fd9..457f3230 100755 --- a/debian/postinst +++ b/debian/postinst @@ -103,6 +103,26 @@ update_ceph_conf() { fi } +ceph_init_exporter_keyring() { + UNIT='ceph-exporter.service' + + # Don't fail in case user has "exotic" configuration where RADOS + # isn't available on all nodes for some reason + /usr/share/pve-manager/helpers/pve-ceph-keyring --init client.exporter || true + + if systemctl -q is-enabled "$UNIT" 2> /dev/null; then + # If the ceph-exporter package was installed previously, the unit will + # most likely have failed, unless the user adapted its configuration. + # Therefore check if it failed and reset it if it did, so that we can + # actually restart it below. + if systemctl -q is-failed "$UNIT" 2> /dev/null; then + systemctl reset-failed "$UNIT" || true + fi + + deb-systemd-invoke restart "$UNIT" || true + fi +} + migrate_apt_auth_conf() { output="" removed="" @@ -213,6 +233,10 @@ case "$1" in update_ceph_conf fi + if test -n "$2" && dpkg --compare-versions "$2" 'lt' '9.0.11'; then + ceph_init_exporter_keyring + fi + if test ! -e /proxmox_install_mode; then # modeled after code generated by dh_start for unit in ${UNITS}; do -- 2.47.3 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel