From 7457f3db0da9963ae1ed580e416bf0dd1337c4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Thu, 18 Dec 2025 17:59:52 +0100 Subject: [PATCH] feat: add admin script apply-ingress-keys-from-globally.pl (#604) --- bin/admin/apply-ingress-keys-from-globally.pl | 49 +++++++++++++++ lib/perl/OVH/Bastion.pm | 2 +- lib/perl/OVH/Bastion/ssh.inc | 63 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100755 bin/admin/apply-ingress-keys-from-globally.pl diff --git a/bin/admin/apply-ingress-keys-from-globally.pl b/bin/admin/apply-ingress-keys-from-globally.pl new file mode 100755 index 0000000..470d447 --- /dev/null +++ b/bin/admin/apply-ingress-keys-from-globally.pl @@ -0,0 +1,49 @@ +#! /usr/bin/env perl +# vim: set filetype=perl ts=4 sw=4 sts=4 et: +use common::sense; +use Getopt::Long; + +use File::Basename; +use lib dirname(__FILE__) . '/../../lib/perl'; +use OVH::Bastion; +use OVH::Result; +use OVH::SimpleLog; + +# this'll be used in syslog +$ENV{'UNIQID'} = OVH::Bastion::generate_uniq_id()->value; + +my $fnret; + +# abort early if we're not a master instance +if (!$ENV{'FORCE'}) { + if (OVH::Bastion::config('readOnlySlaveMode')->value) { + _log "We're not a master instance, don't do anything"; + exit 0; + } +} + +$fnret = OVH::Bastion::get_account_list(); +if (!$fnret) { + _err "Couldn't get the accounts list: $fnret"; + exit 1; +} + +foreach my $account (sort keys %{$fnret->value || {}}) { + _log "Working on account $account"; + $fnret = OVH::Bastion::ssh_ingress_keys_from_apply_account(account => $account, dryRun => $ENV{'DRYRUN'}); + if ($fnret->err eq 'OK_NO_CHANGE') { + _log "... OK (no change across " . $fnret->value->{'nbkeys'} . " keys)"; + } + elsif ($fnret) { + _log "... $fnret (" + . $fnret->value->{'nbchanged'} + . " keys changed out of " + . $fnret->value->{'nbkeys'} + . " keys)"; + } + else { + _warn "... $fnret"; + } +} + +_log "Done, got " . (OVH::SimpleLog::nb_errors()) . " error(s) and " . (OVH::SimpleLog::nb_warnings()) . " warning(s)."; diff --git a/lib/perl/OVH/Bastion.pm b/lib/perl/OVH/Bastion.pm index ac997d6..2503c0b 100644 --- a/lib/perl/OVH/Bastion.pm +++ b/lib/perl/OVH/Bastion.pm @@ -162,7 +162,7 @@ my %_autoload_files = ( ], password => [qw{ get_hashes_from_password get_password_file get_hashes_list is_valid_hash }], ssh => [ - qw{ has_piv_helper verify_piv get_authorized_keys_from_file add_key_to_authorized_keys_file put_authorized_keys_to_file get_ssh_pub_key_info is_valid_public_key get_from_for_user_key generate_ssh_key get_bastion_ips get_supported_ssh_algorithms_list is_allowed_algo_and_size is_valid_fingerprint print_public_key account_ssh_config_get account_ssh_config_set ssh_ingress_keys_piv_apply is_effective_piv_account_policy_enabled print_accepted_key_algorithms } + qw{ has_piv_helper verify_piv get_authorized_keys_from_file add_key_to_authorized_keys_file put_authorized_keys_to_file get_ssh_pub_key_info is_valid_public_key get_from_for_user_key generate_ssh_key get_bastion_ips get_supported_ssh_algorithms_list is_allowed_algo_and_size is_valid_fingerprint print_public_key account_ssh_config_get account_ssh_config_set ssh_ingress_keys_piv_apply is_effective_piv_account_policy_enabled print_accepted_key_algorithms ssh_ingress_keys_from_apply_account } ], ); diff --git a/lib/perl/OVH/Bastion/ssh.inc b/lib/perl/OVH/Bastion/ssh.inc index db16a2c..692069a 100644 --- a/lib/perl/OVH/Bastion/ssh.inc +++ b/lib/perl/OVH/Bastion/ssh.inc @@ -1117,4 +1117,67 @@ sub print_accepted_key_algorithms { return; } +# apply the ingressKeysFrom configuration to all existing ingress keys of this account, +# overriding all their previous "from" configuration, if any +sub ssh_ingress_keys_from_apply_account { + my %params = @_; + my $account = $params{'account'}; + my $dryRun = $params{'dryRun'}; + + my $fnret; + + $fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $account); + $fnret or return $fnret; + $account = $fnret->value->{'account'}; + + my $dir = $fnret->value->{'dir'}; + + $fnret = OVH::Bastion::get_authorized_keys_from_file( + account => $account, + file => $dir . '/' . OVH::Bastion::AK_FILE(), + includePivDisabled => 1 + ); + $fnret or return $fnret; + + my $keys = $fnret->value(); + my $changed = 0; + + foreach my $key (@{$keys || []}) { + my @previousFromList = sort @{$key->{'fromList'} || []}; + $fnret = get_from_for_user_key(key => $key); + $fnret or return $fnret; + + if ($fnret->value && $fnret->value->{'key'}) { + $key = $fnret->value->{'key'}; # replace our key by the one with the proper 'from' + my @newFromList = sort @{$key->{'fromList'} || []}; + + # did we have a change? + $changed++ if (join('', @previousFromList) ne join('', @newFromList)); + } + } + + if ($changed) { + if ($dryRun) { + return R('OK_DRY_RUN', value => {nbchanged => $changed, nbkeys => scalar(@{$keys || []})}); + } + + $fnret = OVH::Bastion::put_authorized_keys_to_file( + account => $account, + file => $dir . '/' . OVH::Bastion::AK_FILE(), + data => $keys + ); + $fnret or return $fnret; + + OVH::Bastion::syslogFormatted( + severity => 'info', + type => 'account', + fields => [[action => 'modify'], [account => $account], [item => 'ingress_keys_from_apply'],] + ); + + return R('OK', value => {nbchanged => $changed, nbkeys => scalar(@{$keys || []})}); + } + + return R('OK_NO_CHANGE', value => {nbchanged => 0, nbkeys => scalar(@{$keys || []})}); +} + 1;