feat: add assetForgetHostKey

pull/517/head
Stéphane Lesimple 1 year ago committed by Stéphane Lesimple
parent 62613bf894
commit 92bc512050

@ -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 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'");
}
#<HEADER
#>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 (!-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 $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}
)
);

@ -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 <HOST|IP> [--port <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);

@ -0,0 +1,24 @@
===================
assetForgetHostKey
===================
Remove the host key of a given asset from all accounts' known hosts
===================================================================
.. admonition:: usage
:class: cmdusage
--osh assetForgetHostKey --host <HOST|IP> [--port <PORT>]
.. program:: assetForgetHostKey
.. option:: --host HOST|IP
Asset whose host key should be removed
.. option:: --port PORT
Asset port serving SSH (default: 22)

@ -25,6 +25,7 @@ restricted plugins
accountUnexpire
accountUnfreeze
accountUnlock
assetForgetHostKey
groupCreate
groupDelete
realmCreate

@ -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 *

@ -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);

@ -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
Loading…
Cancel
Save