diff --git a/bin/helper/osh-assetForgetHostKey b/bin/helper/osh-assetForgetHostKey new file mode 100755 index 000000000..194e5fe61 --- /dev/null +++ b/bin/helper/osh-assetForgetHostKey @@ -0,0 +1,119 @@ +#! /usr/bin/perl -T +# vim: set filetype=perl ts=4 sw=4 sts=4 et: +# NEEDGROUP osh-assetForgetHostKey +# SUDOERS %osh-assetForgetHostKey ALL=(root) NOPASSWD:/usr/bin/env perl -T /opt/bastion/bin/helper/osh-assetForgetHostKey * +# FILEMODE 0700 +# FILEOWN 0 0 + +#>HEADER +use common::sense; +use Getopt::Long qw(:config no_auto_abbrev no_ignore_case); +use Net::IP; +use DateTime; + +use File::Basename; +use lib dirname(__FILE__) . '/../../lib/perl'; +use OVH::Result; +use OVH::Bastion; +use OVH::Bastion::Helper; + +# Fetch command options +my $fnret; +my ($result, @optwarns); +my ($ip, $port); +eval { + local $SIG{__WARN__} = sub { push @optwarns, shift }; + $result = GetOptions( + "ip=s" => sub { $ip //= $_[1] }, + "port=i" => sub { $port //= $_[1] }, + ); +}; +if ($@) { die $@ } + +if (!$result) { + local $" = ", "; + HEXIT('ERR_BAD_OPTIONS', msg => "Error parsing options: @optwarns"); +} + +OVH::Bastion::Helper::check_spurious_args(); + +if (not $ip or not $port) { + HEXIT('ERR_MISSING_PARAMETER', msg => "Missing argument 'ip' or 'port'"); +} + +#
CODE + +# Build the regex we'll be looking for. +my $re; +if ($port == 22) { + # format is "IP ssh-..." + $re = qr/^\Q$ip ssh-\E/m; +} +else { + # format is "[IP]:port ssh-..." + $re = qr/^\Q[$ip]:$port ssh-\E/m; +} + +# First, get all bastion accounts, including realm sysaccounts +$fnret = OVH::Bastion::get_account_list(); +$fnret or HEXIT($fnret); +my %accounts = %{$fnret->value || {}}; + +$fnret = OVH::Bastion::get_realm_list(); +$fnret or HEXIT($fnret); +foreach my $realmName (keys %{$fnret->value || {}}) { + $accounts{$fnret->value->{$realmName}{'sysaccount'}} = $fnret->value->{$realmName}; +} + +my $nbchanges = 0; +my $now = DateTime->now()->iso8601() . 'Z'; +foreach my $name (keys %accounts) { + my $accountHome = $accounts{$name}{'home'}; + if (!-d $accountHome) { + warn_syslog("Account '$name' home '$accountHome' doesn't exist"); + next; + } + + my $knownHosts = "$accountHome/.ssh/known_hosts"; + if (!-e -f $knownHosts) { + # This can happen if the account has never been used yet + next; + } + + # now, slurp the file and look for the host we're being asked about + if (open(my $fh, '<', $knownHosts)) { + my $contents = do { + local $/; + <$fh>; + }; + close($fh); + #my $nbfound = grep { $_ =~ $re } split(/\n/, $contents || ''); + my $nbmatches = $contents =~ s/$re/# removed by $self at $now in session with uniqid $ENV{'UNIQID'}: $&/g; + # remove found lines if any + if ($nbmatches) { + osh_info("Removing $nbmatches lines from ${name}'s known_hosts file"); + if (open($fh, '>', $knownHosts)) { + print $fh $contents; + close($fh); + $nbchanges++; + } + else { + osh_warn("Couldn't adjust ${name}'s known_hosts file"); + warn_syslog("Error while opening $knownHosts file for write: $!"); + } + } + } + else { + warn_syslog("Couldn't open '$knownHosts': $!"); + } +} + +HEXIT( + R( + $nbchanges ? 'OK' : 'OK_NO_CHANGE', + msg => "Finally modified $nbchanges known_hosts accounts' files", + value => {changed_files => $nbchanges} + ) +); diff --git a/bin/plugin/restricted/assetForgetHostKey b/bin/plugin/restricted/assetForgetHostKey new file mode 100755 index 000000000..75eab0e72 --- /dev/null +++ b/bin/plugin/restricted/assetForgetHostKey @@ -0,0 +1,43 @@ +#! /usr/bin/env perl +# vim: set filetype=perl ts=4 sw=4 sts=4 et: +use common::sense; + +use File::Basename; +use lib dirname(__FILE__) . '/../../../lib/perl'; +use OVH::Result; +use OVH::Bastion; +use OVH::Bastion::Plugin qw( :DEFAULT help ); + +my $remainingOptions = OVH::Bastion::Plugin::begin( + argv => \@ARGV, + header => "remove the host key of a given asset from all accounts' known hosts", + options => {}, + helptext => <<'EOF', +Remove the host key of a given asset from all accounts' known hosts + +Usage: --osh SCRIPT_NAME --host [--port ] + + --host HOST|IP Asset whose host key should be removed + --port PORT Asset port serving SSH (default: 22) +EOF +); + +if (!$ip) { + help(); + osh_exit 'ERR_MISSING_PARAMETER', "Missing mandatory parameter --host (or host didn't resolve correctly)"; +} + +# IP can't be a prefix +if ($ip =~ m{/}) { + help(); + osh_exit 'ERR_INVALID_PARAMETER', "Specified IP must not be a prefix ($ip)"; +} + +osh_info "Removing $ip host key from accounts..."; + +my @command = qw{ sudo -n -u root -- /usr/bin/env perl -T }; +push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-assetForgetHostKey'; +push @command, '--ip', $ip; +push @command, '--port', ($port ? $port : 22); + +osh_exit OVH::Bastion::helper(cmd => \@command); diff --git a/etc/sudoers.d/osh-plugin-assetForgetHostKey b/etc/sudoers.d/osh-plugin-assetForgetHostKey new file mode 100644 index 000000000..0f32cc457 --- /dev/null +++ b/etc/sudoers.d/osh-plugin-assetForgetHostKey @@ -0,0 +1,2 @@ +# to modify all accounts' known_hosts we need to be root +%osh-assetForgetHostKey ALL=(root) NOPASSWD:/usr/bin/env perl -T /opt/bastion/bin/helper/osh-assetForgetHostKey * diff --git a/lib/perl/OVH/Bastion/allowkeeper.inc b/lib/perl/OVH/Bastion/allowkeeper.inc index db4d43718..68339877a 100644 --- a/lib/perl/OVH/Bastion/allowkeeper.inc +++ b/lib/perl/OVH/Bastion/allowkeeper.inc @@ -911,9 +911,19 @@ sub get_realm_list { cache => 1 ); + my $entry = $fnret->value->{$name}; + # add proper realms - $name =~ s{^realm_}{}; - $users{$name} = {name => $name}; + my $realmName = $entry->{'name'}; + $realmName =~ s{^realm_}{}; + $users{$realmName} = { + sysaccount => $name, + name => $realmName, + gid => $entry->{'gid'}, + home => $entry->{'dir'}, + shell => $entry->{'shell'}, + uid => $entry->{'uid'}, + }; } return R('OK', value => \%users); diff --git a/tests/functional/tests.d/345-assetforgethostkey.sh b/tests/functional/tests.d/345-assetforgethostkey.sh new file mode 100644 index 000000000..3aaca3484 --- /dev/null +++ b/tests/functional/tests.d/345-assetforgethostkey.sh @@ -0,0 +1,73 @@ +# vim: set filetype=sh ts=4 sw=4 sts=4 et: +# shellcheck shell=bash +# shellcheck disable=SC2086,SC2016,SC2046 +# below: convoluted way that forces shellcheck to source our caller +# shellcheck source=tests/functional/launch_tests_on_instance.sh +. "$(dirname "${BASH_SOURCE[0]}")"/dummy + +testsuite_assetforgethostkey() +{ + # create a1 and a2 + grant accountCreate + + success create_account1 $a0 --osh accountCreate --account $account1 --uid $uid1 --public-key \""$(cat $account1key1file.pub)"\" + json .error_code OK .command accountCreate .value null + + success create_account2 $a0 --osh accountCreate --account $account2 --uid $uid2 --public-key \""$(cat $account2key1file.pub)"\" + json .error_code OK .command accountCreate .value null + + revoke accountCreate + + # grant personal accesses to these accounts + grant accountAddPersonalAccess + + success a0_allow_a1_localhost $a0 --osh accountAddPersonalAccess --account $account1 --host 127.0.0.0/24 --port '*' --user '*' + json .error_code OK .command accountAddPersonalAccess + + success a0_allow_a2_localhost $a0 --osh accountAddPersonalAccess --account $account2 --host 127.0.0.0/24 --port '*' --user '*' + json .error_code OK .command accountAddPersonalAccess + + revoke accountAddPersonalAccess + + # connect to localhost from these accounts (it won't work in the end but their known_hosts files will be updated and that's what we need) + run a1_connect_localhost1 $a1 user1@127.0.0.1 + contain "Connecting..." + + run a2_connect_localhost1_226 $a2 user1@127.0.0.1 -p 226 + contain "Connecting..." + + run a2_connect_localhost1 $a2 user1@127.0.0.1 + contain "Connecting..." + + run a2_connect_localhost2 $a2 user1@127.0.0.2 + contain "Connecting..." + + grant assetForgetHostKey + + # now, delete the host keys for 127.0.0.1 + success a0_asset_forgethostkey $a0 --osh assetForgetHostKey --host 127.0.0.1 + json .error_code OK .command assetForgetHostKey .value.changed_files 2 + + success a0_asset_forgethostkey_dupe $a0 --osh assetForgetHostKey --host 127.0.0.1 + json .error_code OK_NO_CHANGE .command assetForgetHostKey .value.changed_files 0 + + # same but with port 226 + success a0_asset_forgethostkey_226 $a0 --osh assetForgetHostKey --host 127.0.0.1 --port 226 + json .error_code OK .command assetForgetHostKey .value.changed_files 1 + + success a0_asset_forgethostkey_226_dupe $a0 --osh assetForgetHostKey --host 127.0.0.1 --port 226 + json .error_code OK_NO_CHANGE .command assetForgetHostKey .value.changed_files 0 + + revoke assetForgetHostKey + + # delete those accounts + grant accountDelete + + success account1_cleanup $a0 --osh accountDelete --account $account1 --no-confirm + success account2_cleanup $a0 --osh accountDelete --account $account2 --no-confirm + + revoke accountDelete +} + +testsuite_assetforgethostkey +unset -f testsuite_assetforgethostkey