Skip to content

Commit

Permalink
feat: add assetForgetHostKey
Browse files Browse the repository at this point in the history
  • Loading branch information
speed47 committed Dec 4, 2024
1 parent 55f276e commit 2d6f043
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 2 deletions.
119 changes: 119 additions & 0 deletions bin/helper/osh-assetForgetHostKey
Original file line number Diff line number Diff line change
@@ -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'");
}

#<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 (!-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}
)
);
43 changes: 43 additions & 0 deletions bin/plugin/restricted/assetForgetHostKey
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 2 additions & 0 deletions etc/sudoers.d/osh-plugin-assetForgetHostKey
Original file line number Diff line number Diff line change
@@ -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 *
14 changes: 12 additions & 2 deletions lib/perl/OVH/Bastion/allowkeeper.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
73 changes: 73 additions & 0 deletions tests/functional/tests.d/345-assetforgethostkey.sh
Original file line number Diff line number Diff line change
@@ -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 [email protected]
contain "Connecting..."

run a2_connect_localhost1_226 $a2 [email protected] -p 226
contain "Connecting..."

run a2_connect_localhost1 $a2 [email protected]
contain "Connecting..."

run a2_connect_localhost2 $a2 [email protected]
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

0 comments on commit 2d6f043

Please sign in to comment.