mirror of
https://github.com/ovh/the-bastion.git
synced 2025-09-11 23:44:15 +08:00
feat: add --all to groupInfo and accountInfo
This commit is contained in:
parent
a1812e34bb
commit
7a825aeec4
11 changed files with 993 additions and 565 deletions
|
@ -13,90 +13,131 @@ use OVH::Bastion::Plugin qw( :DEFAULT help );
|
||||||
# globally allow sys_getpw* and sys_getgr* cache use
|
# globally allow sys_getpw* and sys_getgr* cache use
|
||||||
$ENV{'PW_GR_CACHE'} = 1;
|
$ENV{'PW_GR_CACHE'} = 1;
|
||||||
|
|
||||||
my ($group);
|
my $withKeys = 1;
|
||||||
|
|
||||||
|
sub toggle_all {
|
||||||
|
my $v = shift;
|
||||||
|
$withKeys = $v;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
my $remainingOptions = OVH::Bastion::Plugin::begin(
|
my $remainingOptions = OVH::Bastion::Plugin::begin(
|
||||||
argv => \@ARGV,
|
|
||||||
header => "group info",
|
argv => \@ARGV,
|
||||||
options => {'group=s' => \$group},
|
header => "group info",
|
||||||
|
options => {
|
||||||
|
'group=s' => \my $group,
|
||||||
|
'all' => \my $all,
|
||||||
|
'with-keys' => sub { $withKeys = 1 },
|
||||||
|
'without-keys' => sub { $withKeys = 0 },
|
||||||
|
'with-everything' => sub { toggle_all(1) },
|
||||||
|
'without-everything' => sub { toggle_all(0) },
|
||||||
|
},
|
||||||
helptext => <<'EOF',
|
helptext => <<'EOF',
|
||||||
Print some basic information about a group
|
Print some basic information about a group
|
||||||
|
|
||||||
Usage: --osh SCRIPT_NAME --group GROUP
|
Usage: --osh SCRIPT_NAME <--group GROUP|--all> [OPTIONS]
|
||||||
|
|
||||||
--group GROUP specify the group to display the infos of
|
--group GROUP Specify the group to display the info of
|
||||||
|
--all Dump info for all groups (auditors only), use with ``--json``
|
||||||
|
|
||||||
|
--with[out]-everything Include or exclude all below options, including future ones
|
||||||
|
--with[out]-keys Whether to include the group keys list (slow-ish, default: yes)
|
||||||
EOF
|
EOF
|
||||||
);
|
);
|
||||||
|
|
||||||
#
|
|
||||||
# code
|
|
||||||
#
|
|
||||||
my $fnret;
|
my $fnret;
|
||||||
|
|
||||||
#
|
if (!$group && !$all) {
|
||||||
# params check
|
|
||||||
#
|
|
||||||
if (!$group) {
|
|
||||||
help();
|
help();
|
||||||
osh_exit 'ERR_MISSING_PARAMETER', "Missing 'group' parameter";
|
osh_exit 'ERR_MISSING_PARAMETER', "Missing '--group' or '--all' parameter";
|
||||||
}
|
}
|
||||||
|
|
||||||
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key");
|
my $isAuditor = OVH::Bastion::is_auditor(account => $self);
|
||||||
$fnret or osh_exit($fnret);
|
if (!$isAuditor && $all) {
|
||||||
|
osh_exit 'ERR_ACCESS_DENIED', "Only bastion auditors can use --all";
|
||||||
# get returned untainted value
|
|
||||||
$group = $fnret->value->{'group'};
|
|
||||||
my $shortGroup = $fnret->value->{'shortGroup'};
|
|
||||||
|
|
||||||
my %roles;
|
|
||||||
foreach my $role (qw{ member aclkeeper gatekeeper owner }) {
|
|
||||||
$fnret = OVH::Bastion::is_group_existing(group => $group . ($role eq 'member' ? '' : "-$role"));
|
|
||||||
if (!$fnret) {
|
|
||||||
osh_exit($fnret) if $role eq 'member'; # critical
|
|
||||||
$roles{$role} = [];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$roles{$role} = [grep { $_ ne 'allowkeeper' } @{$fnret->value->{'members'} || []}];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
my $result_hash = {group => $shortGroup};
|
my @groupsToCheck;
|
||||||
$result_hash->{'owners'} = $roles{'owner'};
|
if ($all) {
|
||||||
$result_hash->{'aclkeepers'} = $roles{'aclkeeper'};
|
$fnret = OVH::Bastion::get_group_list();
|
||||||
$result_hash->{'gatekeepers'} = $roles{'gatekeeper'};
|
$fnret or osh_exit($fnret);
|
||||||
|
@groupsToCheck = sort keys %{$fnret->value};
|
||||||
|
osh_info("Gathering data, this may take a few seconds...");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
@groupsToCheck = ($group);
|
||||||
|
}
|
||||||
|
|
||||||
osh_info "Group "
|
# check all groups and get the untainted data
|
||||||
. $shortGroup
|
my @groups;
|
||||||
. "'s Owners are: "
|
foreach my $groupName (@groupsToCheck) {
|
||||||
. colored(@{$roles{'owner'}} ? join(" ", sort @{$roles{'owner'}}) : '-', "red");
|
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $groupName, groupType => "key");
|
||||||
osh_info "Group "
|
$fnret or osh_exit($fnret);
|
||||||
. $shortGroup
|
|
||||||
. "'s GateKeepers (managing the members/guests list) are: "
|
|
||||||
. colored(@{$roles{'gatekeeper'}} ? join(" ", sort @{$roles{'gatekeeper'}}) : '-', "red");
|
|
||||||
if ( OVH::Bastion::is_group_owner(group => $shortGroup, account => $self, superowner => 1)
|
|
||||||
|| OVH::Bastion::is_group_gatekeeper(group => $shortGroup, account => $self)
|
|
||||||
|| OVH::Bastion::is_group_aclkeeper(group => $shortGroup, account => $self)
|
|
||||||
|| OVH::Bastion::is_group_member(group => $shortGroup, account => $self)
|
|
||||||
|| OVH::Bastion::is_auditor(account => $self))
|
|
||||||
{
|
|
||||||
|
|
||||||
osh_info "Group "
|
# get returned untainted value
|
||||||
. $shortGroup
|
push @groups, {group => $fnret->value->{'group'}, shortGroup => $fnret->value->{'shortGroup'}};
|
||||||
. "'s ACLKeepers (managing the group servers list) are: "
|
}
|
||||||
. colored(@{$roles{'aclkeeper'}} ? join(" ", sort @{$roles{'aclkeeper'}}) : '-', "red");
|
|
||||||
|
|
||||||
# now, who is member / guest ?
|
# gather this only once
|
||||||
my (@members, @guests);
|
$fnret = OVH::Bastion::get_bastion_ips();
|
||||||
foreach my $account (@{$roles{'member'}}) {
|
my $from;
|
||||||
osh_debug("what is $account?");
|
if ($fnret) {
|
||||||
if ($account =~ /^realm_(.+)/) {
|
my @ips = @{$fnret->value};
|
||||||
my $pRealm = $1;
|
$from = 'from="' . join(',', @ips) . '"';
|
||||||
$fnret = OVH::Bastion::get_remote_accounts_from_realm(realm => $pRealm);
|
}
|
||||||
if (!$fnret || !@{$fnret->value}) {
|
|
||||||
|
|
||||||
# we couldn't get the list, or the list is empty: at least show that the realm shared account is there
|
# big hash containing all the data we want to return
|
||||||
push @members, $user;
|
my %return;
|
||||||
}
|
|
||||||
else {
|
foreach my $groupData (@groups) {
|
||||||
|
$group = $groupData->{'group'};
|
||||||
|
my $shortGroup = $groupData->{'shortGroup'};
|
||||||
|
|
||||||
|
# get the member list of each system group mapped to one of the roles of the group
|
||||||
|
my %roles;
|
||||||
|
foreach my $role (qw{ member aclkeeper gatekeeper owner }) {
|
||||||
|
$fnret = OVH::Bastion::is_group_existing(group => $group . ($role eq 'member' ? '' : "-$role"));
|
||||||
|
if (!$fnret) {
|
||||||
|
osh_exit($fnret) if $role eq 'member'; # if this happens, we really have a problem here
|
||||||
|
$roles{$role} = [];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$roles{$role} = [grep { $_ ne 'allowkeeper' } @{$fnret->value->{'members'} || []}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# data that anybody can view:
|
||||||
|
my %ret = (
|
||||||
|
group => $shortGroup,
|
||||||
|
owners => $roles{'owner'},
|
||||||
|
gatekeepers => $roles{'gatekeeper'}
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $isAuditor
|
||||||
|
|| OVH::Bastion::is_group_owner(group => $shortGroup, account => $self, superowner => 1)
|
||||||
|
|| OVH::Bastion::is_group_gatekeeper(group => $shortGroup, account => $self)
|
||||||
|
|| OVH::Bastion::is_group_aclkeeper(group => $shortGroup, account => $self)
|
||||||
|
|| OVH::Bastion::is_group_member(group => $shortGroup, account => $self))
|
||||||
|
{
|
||||||
|
# members, aclkeepers, gatekeepers, owners and auditors can get the aclkeepers list
|
||||||
|
$ret{'aclkeepers'} = $roles{'aclkeeper'};
|
||||||
|
|
||||||
|
# being a member of the system group corresponding to the bastion group
|
||||||
|
# can mean either member or guest, so check this here, taking into account the
|
||||||
|
# case of the realm accounts
|
||||||
|
my (@members, @guests);
|
||||||
|
foreach my $account (@{$roles{'member'}}) {
|
||||||
|
# realm accounts
|
||||||
|
if ($account =~ /^realm_(.+)/) {
|
||||||
|
my $pRealm = $1;
|
||||||
|
$fnret = OVH::Bastion::get_remote_accounts_from_realm(realm => $pRealm);
|
||||||
|
if (!$fnret || !@{$fnret->value}) {
|
||||||
|
# we couldn't get the list, or the list is empty: at least show that the realm shared account is there
|
||||||
|
push @members, $user;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
# show remote realm accounts names, either as guests or members
|
||||||
foreach my $pRemoteaccount (@{$fnret->value}) {
|
foreach my $pRemoteaccount (@{$fnret->value}) {
|
||||||
if (OVH::Bastion::is_group_guest(group => $shortGroup, account => "$pRealm/$pRemoteaccount")) {
|
if (OVH::Bastion::is_group_guest(group => $shortGroup, account => "$pRealm/$pRemoteaccount")) {
|
||||||
push @guests, "$pRealm/$pRemoteaccount";
|
push @guests, "$pRealm/$pRemoteaccount";
|
||||||
|
@ -106,141 +147,192 @@ if ( OVH::Bastion::is_group_owner(group => $shortGroup, account => $self, supe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
# normal case (non-realm accounts)
|
||||||
else {
|
|
||||||
|
|
||||||
if (OVH::Bastion::is_group_guest(account => $account, group => $shortGroup)) {
|
|
||||||
push @guests, $account;
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
push @members, $account;
|
if (OVH::Bastion::is_group_guest(account => $account, group => $shortGroup)) {
|
||||||
|
push @guests, $account;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push @members, $account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# for each guest, get the number of accesses they have on the group,
|
||||||
|
# so we can show it nicely
|
||||||
|
my %guest_nb_accesses;
|
||||||
|
my @filtered_guests;
|
||||||
|
foreach my $guest (sort @guests) {
|
||||||
|
$fnret = OVH::Bastion::get_acl_way(way => 'groupguest', group => $shortGroup, account => $guest);
|
||||||
|
|
||||||
|
# for realms, don't show remote accounts with zero accesses, this could be confusing
|
||||||
|
next if ($guest =~ m{/} && $fnret && @{$fnret->value} == 0);
|
||||||
|
|
||||||
|
$guest_nb_accesses{$guest} = $fnret ? scalar(@{$fnret->value}) : undef;
|
||||||
|
push @filtered_guests, $guest;
|
||||||
|
}
|
||||||
|
|
||||||
|
# deprecated in v2.18.00+
|
||||||
|
$ret{'full_members'} = \@members;
|
||||||
|
$ret{'partial_members'} = \@filtered_guests;
|
||||||
|
# /deprecated
|
||||||
|
|
||||||
|
$ret{'members'} = \@members;
|
||||||
|
$ret{'guests'} = \@filtered_guests;
|
||||||
|
$ret{'guests_accesses'} = \%guest_nb_accesses;
|
||||||
|
|
||||||
|
# add a hint about possibly inactive members
|
||||||
|
my @inactive;
|
||||||
|
foreach my $account (@members) {
|
||||||
|
if (OVH::Bastion::is_account_active(account => $account)->is_ko) {
|
||||||
|
push @inactive, $account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$ret{'inactive'} = \@inactive;
|
||||||
|
|
||||||
|
# policies
|
||||||
|
$fnret = OVH::Bastion::group_config(group => $group, key => 'mfa_required');
|
||||||
|
if ($fnret && $fnret->value ne 'none') {
|
||||||
|
$ret{'mfa_required'} = $fnret->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::group_config(group => $group, key => 'guest_ttl_limit');
|
||||||
|
if ($fnret && defined $fnret->value && $fnret->value =~ /^\d+$/) {
|
||||||
|
$ret{'guest_ttl_limit'} = $fnret->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::group_config(group => $group, %{OVH::Bastion::OPT_GROUP_IDLE_KILL_TIMEOUT()});
|
||||||
|
if ($fnret && defined $fnret->value && $fnret->value =~ /^-?\d+$/) {
|
||||||
|
$ret{'idle_kill_timeout'} = $fnret->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::group_config(group => $group, => %{OVH::Bastion::OPT_GROUP_IDLE_LOCK_TIMEOUT()});
|
||||||
|
if ($fnret && defined $fnret->value && $fnret->value =~ /^-?\d+$/) {
|
||||||
|
$ret{'idle_lock_timeout'} = $fnret->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# group egress keys if we've been asked those
|
||||||
|
if ($withKeys) {
|
||||||
|
$fnret = OVH::Bastion::get_group_keys(group => $group);
|
||||||
|
if ($fnret and $from) {
|
||||||
|
foreach my $keyfile (@{$fnret->value->{'sortedKeys'}}) {
|
||||||
|
my $key = $fnret->value->{'keys'}{$keyfile};
|
||||||
|
$key->{'prefix'} = $from;
|
||||||
|
$ret{'keys'}{$key->{'fingerprint'}} = $key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
osh_info "Group "
|
|
||||||
. $shortGroup
|
|
||||||
. "'s Members (with access to ALL the group servers) are: "
|
|
||||||
. colored(@members ? join(" ", sort @members) : '-', "red");
|
|
||||||
|
|
||||||
my %guest_details;
|
$return{$shortGroup} = \%ret;
|
||||||
|
|
||||||
|
# print all this in a human-readable format, except if we've been asked
|
||||||
|
# to dump the data for all groups, in which case the caller will only use
|
||||||
|
# our JSON output
|
||||||
|
print_group_info(%ret) if !$all;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_group_info {
|
||||||
|
my %ret = @_;
|
||||||
|
my $groupName = $ret{'shortGroup'};
|
||||||
|
|
||||||
|
osh_info("Group ${groupName}'s Owners are: "
|
||||||
|
. colored(@{$ret{'owners'}} ? join(" ", sort @{$ret{'owners'}}) : '-', 'red'))
|
||||||
|
if $ret{'owners'};
|
||||||
|
|
||||||
|
osh_info("Group ${groupName}'s GateKeepers (managing the members/guests list) are: "
|
||||||
|
. colored(@{$ret{'gatekeeper'}} ? join(" ", sort @{$ret{'gatekeepers'}}) : '-', 'red'))
|
||||||
|
if $ret{'gatekeepers'};
|
||||||
|
|
||||||
|
osh_info("Group ${groupName}'s ACLKeepers (managing the group servers list) are: "
|
||||||
|
. colored(@{$ret{'aclkeepers'}} ? join(" ", sort @{$ret{'aclkeepers'}}) : '-', 'red'))
|
||||||
|
if $ret{'aclkeepers'};
|
||||||
|
|
||||||
|
osh_info("Group ${groupName}'s Members (with access to ALL the group servers) are: "
|
||||||
|
. colored(@{$ret{'members'}} ? join(" ", sort @{$ret{'members'}}) : '-', 'red'))
|
||||||
|
if $ret{'members'};
|
||||||
|
|
||||||
|
# show guest info, along with the number of accesses each guest has
|
||||||
my @guest_text;
|
my @guest_text;
|
||||||
my @filtered_guests;
|
foreach my $guest (@{$ret{'guests'}}) {
|
||||||
foreach my $guest (sort @guests) {
|
my $nb = $ret{'guest_accesse'}{$guest};
|
||||||
$fnret = OVH::Bastion::get_acl_way(way => 'groupguest', group => $shortGroup, account => $guest);
|
push @guest_text, sprintf("%s[%s]", $guest, $nb // '?');
|
||||||
|
|
||||||
# for realms, don't show remote accounts with zero accesses, this could be confusing
|
|
||||||
next if ($guest =~ m{/} && $fnret && @{$fnret->value} == 0);
|
|
||||||
$guest_details{$guest} = $fnret ? scalar(@{$fnret->value}) : '?';
|
|
||||||
push @guest_text, $guest . "[" . $guest_details{$guest} . "]";
|
|
||||||
push @filtered_guests, $guest;
|
|
||||||
}
|
}
|
||||||
osh_info "Group "
|
osh_info("Group ${groupName}'s Guests (with access to SOME of the group servers) are: "
|
||||||
. $shortGroup
|
. colored(@{$ret{'guests'}} ? join(" ", sort @guest_text) : '-', 'red'))
|
||||||
. "'s Guests (with access to SOME of the group servers) are: "
|
if $ret{'guests'};
|
||||||
. colored(@filtered_guests ? join(" ", @guest_text) : '-', "red");
|
|
||||||
|
|
||||||
# deprecated in v2.18.00+
|
# current user doesn't have enough rights to get this info, tell them that
|
||||||
$result_hash->{'full_members'} = \@members;
|
if (!$ret{'members'}) {
|
||||||
$result_hash->{'partial_members'} = \@filtered_guests;
|
osh_info "You should ask them if you think you need access for your work tasks.";
|
||||||
|
}
|
||||||
|
|
||||||
# /deprecated
|
if (@{$ret{'inactive'}}) {
|
||||||
|
osh_info("For your information, the following accounts are inactive: "
|
||||||
|
. colored(join(" ", @{$ret{'inactive'}}), "blue"));
|
||||||
|
}
|
||||||
|
|
||||||
$result_hash->{'members'} = \@members;
|
if ($ret{'mfa_required'}) {
|
||||||
$result_hash->{'guests'} = \@filtered_guests;
|
my %mfa2text = (
|
||||||
$result_hash->{'guests_accesses'} = \%guest_details;
|
"any" => "",
|
||||||
|
"totp" => " (TOTP)",
|
||||||
|
"password" => " (password)",
|
||||||
|
);
|
||||||
|
osh_warn("MFA Required: when connecting to servers of this group, users will be asked for an "
|
||||||
|
. "additional authentication factor"
|
||||||
|
. $mfa2text{$ret{'mfa_required'}});
|
||||||
|
}
|
||||||
|
|
||||||
my @inactive;
|
if ($ret{'guest_ttl_limit'}) {
|
||||||
foreach my $account (@members) {
|
osh_warn("Guest TTL enforced: guest accesses must have a TTL with a maximum duration of "
|
||||||
if (OVH::Bastion::is_account_active(account => $account)->is_ko) {
|
. OVH::Bastion::duration2human(seconds => $ret{'guest_ttl_limit'})->value->{'duration'});
|
||||||
push @inactive, $account;
|
}
|
||||||
|
|
||||||
|
if ($ret{'idle_kill_timeout'}) {
|
||||||
|
my $action = "NOT be cut";
|
||||||
|
if ($ret{'idle_kill_timeout'} > 0) {
|
||||||
|
$action =
|
||||||
|
"be cut after " . OVH::Bastion::duration2human(seconds => $ret{'idle_kill_timeout'})->value->{'duration'};
|
||||||
}
|
}
|
||||||
}
|
osh_warn "Specific idle kill timeout: idle sessions on servers of this group will $action";
|
||||||
if (@inactive) {
|
|
||||||
osh_info "For your information, the following accounts are inactive: " . colored(join(" ", @inactive), "blue");
|
|
||||||
$result_hash->{'inactive'} = \@inactive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# policies
|
if ($ret{'idle_lock_timeout'}) {
|
||||||
$fnret = OVH::Bastion::group_config(group => $group, key => 'mfa_required');
|
my $action = "NOT be locked";
|
||||||
if ($fnret && $fnret->value eq 'password') {
|
if ($ret{'idle_lock_timeout'} > 0) {
|
||||||
osh_warn
|
$action = "be locked after "
|
||||||
"MFA Required: when connecting to servers of this group, users will be asked for an additional authentication factor (password)";
|
. OVH::Bastion::duration2human(seconds => $ret{'idle_lock_timeout'})->value->{'duration'};
|
||||||
}
|
}
|
||||||
elsif ($fnret && $fnret->value eq 'totp') {
|
osh_warn "Specific idle kill timeout: idle sessions on servers of this group will $action";
|
||||||
osh_warn
|
|
||||||
"MFA Required: when connecting to servers of this group, users will be asked for an additional authentication factor (TOTP)";
|
|
||||||
}
|
|
||||||
elsif ($fnret && $fnret->value eq 'any') {
|
|
||||||
osh_warn
|
|
||||||
"MFA Required: When connecting to servers of this group, users will be asked for an additional authentication factor";
|
|
||||||
}
|
|
||||||
if ($fnret && $fnret->value ne 'none') {
|
|
||||||
$result_hash->{'mfa_required'} = $fnret->value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$fnret = OVH::Bastion::group_config(group => $group, key => 'guest_ttl_limit');
|
if ($withKeys) {
|
||||||
if ($fnret && defined $fnret->value && $fnret->value =~ /^\d+$/) {
|
osh_info ' ';
|
||||||
osh_warn "Guest TTL enforced: guest accesses must have a TTL with a maximum duration of "
|
if (!%{$ret{'keys'}}) {
|
||||||
. OVH::Bastion::duration2human(seconds => $fnret->value)->value->{'duration'};
|
osh_info "This group has no SSH egress key, the owner may use groupGenerateEgressKey to generate one.";
|
||||||
$result_hash->{'guest_ttl_limit'} = $fnret->value;
|
}
|
||||||
}
|
elsif (keys %{$ret{'keys'}} == 1) {
|
||||||
|
osh_info "The public key of this group is:";
|
||||||
$fnret = OVH::Bastion::group_config(group => $group, %{OVH::Bastion::OPT_GROUP_IDLE_KILL_TIMEOUT()});
|
|
||||||
if ($fnret && defined $fnret->value && $fnret->value =~ /^-?\d+$/) {
|
|
||||||
if ($fnret->value == 0) {
|
|
||||||
osh_warn "Specific idle kill timeout: idle sessions on servers of this group will NOT be cut";
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
osh_warn "Specific idle kill timeout: idle sessions on servers of this group will be cut after "
|
osh_info "The public key of this group are:";
|
||||||
. OVH::Bastion::duration2human(seconds => $fnret->value)->value->{'duration'};
|
}
|
||||||
|
osh_info ' ';
|
||||||
|
my @sorted = sort { $ret{'keys'}{$a}{'mtime'} <=> $ret{'keys'}{$b}{'mtime'} } keys %{$ret{'keys'}};
|
||||||
|
foreach my $fingerprint (@sorted) {
|
||||||
|
OVH::Bastion::print_public_key(key => $ret{'keys'}{$fingerprint});
|
||||||
}
|
}
|
||||||
$result_hash->{'idle_kill_timeout'} = $fnret->value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$fnret = OVH::Bastion::group_config(group => $group, => %{OVH::Bastion::OPT_GROUP_IDLE_LOCK_TIMEOUT()});
|
return;
|
||||||
if ($fnret && defined $fnret->value && $fnret->value =~ /^-?\d+$/) {
|
}
|
||||||
if ($fnret->value == 0) {
|
|
||||||
osh_warn "Specific idle lock timeout: idle sessions on servers of this group will NOT be locked";
|
if (!$all) {
|
||||||
}
|
# only one group, don't return a hash of hash to keep backward compat
|
||||||
else {
|
my @keys = keys %return;
|
||||||
osh_warn "Specific idle lock timeout: idle sessions on servers of this group will be locked after "
|
osh_ok $return{$keys[0]};
|
||||||
. OVH::Bastion::duration2human(seconds => $fnret->value)->value->{'duration'};
|
|
||||||
}
|
|
||||||
$result_hash->{'idle_lock_timeout'} = $fnret->value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
osh_info "You should ask him/her/them if you think you need access for your work tasks.";
|
osh_info "If you're only seeing this line, you might want to use --json";
|
||||||
|
osh_ok \%return;
|
||||||
}
|
}
|
||||||
|
|
||||||
# get pubkeys with the proper from='' and show them
|
|
||||||
|
|
||||||
$fnret = OVH::Bastion::get_bastion_ips();
|
|
||||||
my $from;
|
|
||||||
if ($fnret) {
|
|
||||||
my @ips = @{$fnret->value};
|
|
||||||
$from = 'from="' . join(',', @ips) . '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
$fnret = OVH::Bastion::get_group_keys(group => $group);
|
|
||||||
if ($fnret and $from) {
|
|
||||||
osh_info ' ';
|
|
||||||
if ($fnret->value && !@{$fnret->value->{'sortedKeys'}}) {
|
|
||||||
osh_info "This group has no SSH egress key, the owner may use groupGenerateEgressKey to generate one.";
|
|
||||||
}
|
|
||||||
elsif (@{$fnret->value->{'sortedKeys'}} == 1) {
|
|
||||||
osh_info "The public key of this group is:";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
osh_info "The public keys of this group are:";
|
|
||||||
}
|
|
||||||
osh_info ' ';
|
|
||||||
foreach my $keyfile (@{$fnret->value->{'sortedKeys'}}) {
|
|
||||||
my $key = $fnret->value->{'keys'}{$keyfile};
|
|
||||||
$key->{'prefix'} = $from;
|
|
||||||
OVH::Bastion::print_public_key(key => $key);
|
|
||||||
$result_hash->{'keys'}{$key->{'fingerprint'}} = $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
osh_ok $result_hash;
|
|
||||||
|
|
|
@ -15,329 +15,523 @@ use OVH::Bastion::Plugin qw( :DEFAULT help );
|
||||||
# globally allow sys_getpw* and sys_getgr* cache use
|
# globally allow sys_getpw* and sys_getgr* cache use
|
||||||
$ENV{'PW_GR_CACHE'} = 1;
|
$ENV{'PW_GR_CACHE'} = 1;
|
||||||
|
|
||||||
|
my $withGroups = 0;
|
||||||
|
my $withPasswordInfo = 0;
|
||||||
|
my $withEgressKeys = 0;
|
||||||
|
|
||||||
|
sub toggle_all {
|
||||||
|
my $v = shift;
|
||||||
|
$withGroups = $v;
|
||||||
|
$withPasswordInfo = $v;
|
||||||
|
$withEgressKeys = $v;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
OVH::Bastion::Plugin::begin(
|
OVH::Bastion::Plugin::begin(
|
||||||
argv => \@ARGV,
|
argv => \@ARGV,
|
||||||
header => "account information",
|
header => "account information",
|
||||||
options => {'account=s' => \my $account, "list-groups" => \my $listGroups},
|
options => {
|
||||||
|
'account=s' => \my $account,
|
||||||
|
"all" => \my $all,
|
||||||
|
"list-groups|with-groups" => sub { $withGroups = 1 },
|
||||||
|
"without-groups" => sub { $withGroups = 0 },
|
||||||
|
'with-mfa-password-info' => sub { $withPasswordInfo = 1 },
|
||||||
|
'without-mfa-password-info' => sub { $withPasswordInfo = 0 },
|
||||||
|
'with-egress-keys' => sub { $withEgressKeys = 1 },
|
||||||
|
'without-egress-keys' => sub { $withEgressKeys = 0 },
|
||||||
|
'with-everything' => sub { toggle_all(1) },
|
||||||
|
'without-everything' => sub { toggle_all(0) },
|
||||||
|
},
|
||||||
helptext => <<'EOF',
|
helptext => <<'EOF',
|
||||||
Display some information about an account
|
Display some information about an account
|
||||||
|
|
||||||
Usage: --osh SCRIPT_NAME --account ACCOUNT [--list-groups]
|
Usage: --osh SCRIPT_NAME <--account ACCOUNT|--all> [OPTIONS]
|
||||||
|
|
||||||
--account ACCOUNT The account name to work on
|
--account ACCOUNT The account name to work on
|
||||||
--list-groups Show which groups the account has a role on
|
--all Dump info for all accounts (auditors only), use with ``--json``
|
||||||
|
|
||||||
|
--with[out]-everything Include or exclude all below options, including future ones
|
||||||
|
--with[out]-groups Whether to include the groups the account has a role on (SLOW, default: no)
|
||||||
|
--with[out]-mfa-password-info Whether to include MFA password info of the account (SLOW, auditors only, default: no)
|
||||||
|
--with[out]-egress-keys Whether to include the account's egress keys (SLOW, auditors only, default: no)
|
||||||
EOF
|
EOF
|
||||||
);
|
);
|
||||||
|
|
||||||
my $fnret;
|
my $fnret;
|
||||||
|
|
||||||
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $account);
|
# check params
|
||||||
$fnret or osh_exit $fnret;
|
|
||||||
$account = $fnret->value->{'account'};
|
if ($account && $all) {
|
||||||
my $sysaccount = $fnret->value->{'sysaccount'};
|
osh_exit('ERR_INCOMPATIBLE_PARAMETERS', msg => "Can't use both --account and --all");
|
||||||
my $remoteaccount = $fnret->value->{'remoteaccount'};
|
}
|
||||||
|
|
||||||
|
if (($all || $withPasswordInfo || $withEgressKeys) && !OVH::Bastion::is_auditor(account => $self)) {
|
||||||
|
osh_exit('ERR_ACCESS_DENIED', msg => "This option can only be used by bastion auditors");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$account && !$all) {
|
||||||
|
help();
|
||||||
|
osh_exit('ERR_MISSING_PARAMETER', msg => "Missing either --account or --all parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
# gather all accounts if $all, or only use the user-specified account if !$all
|
||||||
|
|
||||||
|
my @accountsToCheck;
|
||||||
|
if ($all) {
|
||||||
|
$fnret = OVH::Bastion::get_account_list();
|
||||||
|
$fnret or osh_exit $fnret;
|
||||||
|
|
||||||
|
@accountsToCheck = sort keys %{$fnret->value};
|
||||||
|
osh_info("Gathering data, this may take a few seconds...");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
@accountsToCheck = ($account);
|
||||||
|
}
|
||||||
|
|
||||||
|
# validate each account and get their corresponding sys/remote name, while also untainting it
|
||||||
|
|
||||||
|
my @accounts;
|
||||||
|
foreach my $anAccount (@accountsToCheck) {
|
||||||
|
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $anAccount);
|
||||||
|
$fnret or osh_exit $fnret;
|
||||||
|
$account = $fnret->value->{'account'};
|
||||||
|
my $sysaccount = $fnret->value->{'sysaccount'};
|
||||||
|
my $remoteaccount = $fnret->value->{'remoteaccount'};
|
||||||
|
push @accounts, {account => $account, sysaccount => $sysaccount, remoteaccount => $remoteaccount};
|
||||||
|
}
|
||||||
|
|
||||||
|
# load these only once
|
||||||
|
|
||||||
$fnret = OVH::Bastion::get_plugin_list(restrictedOnly => 1);
|
$fnret = OVH::Bastion::get_plugin_list(restrictedOnly => 1);
|
||||||
$fnret or osh_exit $fnret;
|
$fnret or osh_exit $fnret;
|
||||||
|
my @commands = sort keys %{$fnret->value};
|
||||||
|
|
||||||
my %ret;
|
my @groups;
|
||||||
if (OVH::Bastion::is_admin(account => $account)) {
|
if ($withGroups) {
|
||||||
osh_info "$account is a bastion " . colored('admin', 'green');
|
|
||||||
$ret{'is_admin'} = 1;
|
|
||||||
}
|
|
||||||
if (OVH::Bastion::is_super_owner(account => $account)) {
|
|
||||||
osh_info "$account is a bastion " . colored('superowner', 'green');
|
|
||||||
$ret{'is_superowner'} = 1;
|
|
||||||
}
|
|
||||||
if (OVH::Bastion::is_auditor(account => $account)) {
|
|
||||||
osh_info "$account is a bastion " . colored('auditor', 'green');
|
|
||||||
$ret{'is_auditor'} = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
osh_info "This account has access to the following restricted commands:";
|
|
||||||
my @granted;
|
|
||||||
foreach my $plugin (sort keys %{$fnret->value}) {
|
|
||||||
$fnret = OVH::Bastion::is_user_in_group(user => $account, group => "osh-$plugin");
|
|
||||||
if ($fnret) {
|
|
||||||
push @granted, $plugin;
|
|
||||||
osh_info "- $plugin";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!@granted) {
|
|
||||||
osh_info "(none)";
|
|
||||||
}
|
|
||||||
$ret{'allowed_commands'} = \@granted;
|
|
||||||
|
|
||||||
my $result_hash = {};
|
|
||||||
|
|
||||||
if ($listGroups) {
|
|
||||||
$fnret = OVH::Bastion::get_group_list();
|
$fnret = OVH::Bastion::get_group_list();
|
||||||
$fnret or osh_exit $fnret;
|
$fnret or osh_exit $fnret;
|
||||||
|
@groups = sort keys %{$fnret->value};
|
||||||
|
}
|
||||||
|
|
||||||
osh_info "\nThis account is part of the following groups:";
|
# gather info from the account(s)
|
||||||
|
|
||||||
foreach my $name (sort keys %{$fnret->value}) {
|
my %return;
|
||||||
my @flags;
|
foreach my $accHash (@accounts) {
|
||||||
push @flags, 'owner' if OVH::Bastion::is_group_owner(group => $name, account => $account);
|
my %ret;
|
||||||
push @flags, 'gatekeeper' if OVH::Bastion::is_group_gatekeeper(group => $name, account => $account);
|
my ($account, $sysaccount, $remoteaccount) =
|
||||||
push @flags, 'aclkeeper' if OVH::Bastion::is_group_aclkeeper(group => $name, account => $account);
|
($accHash->{'account'}, $accHash->{'sysaccount'}, $accHash->{'remoteaccount'});
|
||||||
push @flags, 'member' if OVH::Bastion::is_group_member(group => $name, account => $account);
|
|
||||||
push @flags, 'guest' if OVH::Bastion::is_group_guest(group => $name, account => $account);
|
$ret{'account'} = $account;
|
||||||
if (@flags) {
|
if (OVH::Bastion::is_admin(account => $account)) {
|
||||||
my $line = sprintf "%18s", $name;
|
$ret{'is_admin'} = 1;
|
||||||
|
}
|
||||||
|
if (OVH::Bastion::is_super_owner(account => $account)) {
|
||||||
|
$ret{'is_superowner'} = 1;
|
||||||
|
}
|
||||||
|
if (OVH::Bastion::is_auditor(account => $account)) {
|
||||||
|
$ret{'is_auditor'} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @granted;
|
||||||
|
foreach my $plugin (@commands) {
|
||||||
|
$fnret = OVH::Bastion::is_user_in_group(user => $account, group => "osh-$plugin");
|
||||||
|
push @granted, $plugin if $fnret;
|
||||||
|
}
|
||||||
|
$ret{'allowed_commands'} = \@granted;
|
||||||
|
|
||||||
|
my $groups_hash = {};
|
||||||
|
if ($withGroups) {
|
||||||
|
foreach my $name (@groups) {
|
||||||
|
my @flags;
|
||||||
|
push @flags, 'owner' if OVH::Bastion::is_group_owner(group => $name, account => $account);
|
||||||
|
push @flags, 'gatekeeper' if OVH::Bastion::is_group_gatekeeper(group => $name, account => $account);
|
||||||
|
push @flags, 'aclkeeper' if OVH::Bastion::is_group_aclkeeper(group => $name, account => $account);
|
||||||
|
push @flags, 'member' if OVH::Bastion::is_group_member(group => $name, account => $account);
|
||||||
|
push @flags, 'guest' if OVH::Bastion::is_group_guest(group => $name, account => $account);
|
||||||
|
$groups_hash->{$name} = {flags => \@flags, name => $name} if @flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$ret{'groups'} = $groups_hash;
|
||||||
|
|
||||||
|
my $canConnect = 1;
|
||||||
|
$ret{'always_active'} = OVH::Bastion::account_config(
|
||||||
|
account => $account,
|
||||||
|
key => OVH::Bastion::OPT_ACCOUNT_ALWAYS_ACTIVE,
|
||||||
|
public => 1
|
||||||
|
) ? 1 : 0;
|
||||||
|
if ($ret{'always_active'}) {
|
||||||
|
$ret{'is_active'} = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$fnret = OVH::Bastion::is_account_active(account => $account);
|
||||||
|
if ($fnret->is_ok) {
|
||||||
|
$ret{'is_active'} = 1;
|
||||||
|
}
|
||||||
|
elsif ($fnret->is_ko) {
|
||||||
|
$canConnect = 0;
|
||||||
|
$ret{'is_active'} = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OVH::Bastion::is_auditor(account => $self)) {
|
||||||
|
|
||||||
|
# TTL check
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::is_account_ttl_nonexpired(sysaccount => $sysaccount, account => $account);
|
||||||
|
if ($fnret->is_ok && $fnret->err eq 'OK_NO_TTL') {
|
||||||
|
$ret{'is_ttl_set'} = 0;
|
||||||
|
$ret{'is_ttl_expired'} = 0;
|
||||||
|
}
|
||||||
|
elsif ($fnret->is_ok && $fnret->err eq 'OK_TTL_VALID') {
|
||||||
|
$ret{'is_ttl_set'} = 1;
|
||||||
|
$ret{'is_ttl_expired'} = 0;
|
||||||
|
}
|
||||||
|
elsif ($fnret->is_ko) {
|
||||||
|
$canConnect = 0;
|
||||||
|
$ret{'is_ttl_set'} = 1;
|
||||||
|
$ret{'is_ttl_expired'} = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
osh_warn "Error getting account TTL expiration info (" . $fnret->msg . ")";
|
||||||
|
}
|
||||||
|
$ret{'ttl_timestamp'} =
|
||||||
|
($fnret->value && $fnret->value->{'expiry_time'}) ? $fnret->value->{'expiry_time'} : undef;
|
||||||
|
|
||||||
|
# freeze check
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::is_account_nonfrozen(account => $account);
|
||||||
|
$ret{'is_frozen'} = undef;
|
||||||
|
if ($fnret->is_ok) {
|
||||||
|
$ret{'is_frozen'} = 0;
|
||||||
|
}
|
||||||
|
elsif ($fnret->is_ko) {
|
||||||
|
$ret{'is_frozen'} = 1;
|
||||||
|
$ret{'freeze_info'} = $fnret->value;
|
||||||
|
$canConnect = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# expi check
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::is_account_nonexpired(sysaccount => $sysaccount, remoteaccount => $remoteaccount);
|
||||||
|
if ($fnret->is_ok) {
|
||||||
|
$ret{'is_expired'} = 0;
|
||||||
|
}
|
||||||
|
elsif ($fnret->is_ko) {
|
||||||
|
$canConnect = 0;
|
||||||
|
$ret{'is_expired'} = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
osh_warn "Error getting account expiration info (" . $fnret->msg . ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$fnret->is_err) {
|
||||||
|
$ret{'can_connect'} = $canConnect;
|
||||||
|
if ($fnret->value->{'already_seen_before'}) {
|
||||||
|
$ret{'already_seen_before'} = 1;
|
||||||
|
if (defined $fnret->value->{'seconds'}) {
|
||||||
|
$fnret = OVH::Bastion::duration2human(seconds => $fnret->value->{'seconds'}, tense => "past");
|
||||||
|
if ($fnret) {
|
||||||
|
$ret{'last_activity'}{$_} = $fnret->value->{$_} for qw{ datetime_local datetime_utc };
|
||||||
|
$ret{'last_activity'}{'ago'} = $fnret->value->{'duration'};
|
||||||
|
$ret{'last_activity'}{'timestamp'} = time() - $fnret->value->{'seconds'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ret{'already_seen_before'} = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::account_config(account => $account, key => "creation_info");
|
||||||
|
if ($fnret) {
|
||||||
|
my $creation_info;
|
||||||
|
eval { $creation_info = decode_json($fnret->value); };
|
||||||
|
if ($@) {
|
||||||
|
osh_warn(
|
||||||
|
"While reading creation metadata information for account '$account', couldn't decode JSON: $@");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ret{'creation_information'} = $creation_info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::account_ssh_config_get(account => $account);
|
||||||
|
if ($fnret->err eq 'OK_EMPTY') {
|
||||||
|
$ret{'account_egress_ssh_config'}{'type'} = 'default';
|
||||||
|
}
|
||||||
|
elsif ($fnret->err eq 'ERR_FILE_LOCALLY_MODIFIED') {
|
||||||
|
$ret{'account_egress_ssh_config'}{'type'} = 'locally_modified';
|
||||||
|
}
|
||||||
|
elsif ($fnret) {
|
||||||
|
$ret{'account_egress_ssh_config'}{'type'} = 'custom';
|
||||||
|
foreach my $key (sort keys %{$fnret->value}) {
|
||||||
|
$ret{'account_egress_ssh_config'}{'items'}{$key} = $fnret->value->{$key};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ret{'account_egress_ssh_config'}{'type'} = 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::account_config(
|
||||||
|
account => $account,
|
||||||
|
public => 1,
|
||||||
|
key => OVH::Bastion::OPT_ACCOUNT_INGRESS_PIV_POLICY
|
||||||
|
);
|
||||||
|
$ret{'ingress_piv_enforced'} = ($fnret && $fnret->value eq 'yes') ? 1 : 0; # keep for backwards compat
|
||||||
|
$ret{'ingress_piv_policy'} = $fnret->value || undef;
|
||||||
|
|
||||||
|
$fnret = OVH::Bastion::account_config(
|
||||||
|
account => $account,
|
||||||
|
public => 1,
|
||||||
|
key => OVH::Bastion::OPT_ACCOUNT_INGRESS_PIV_GRACE
|
||||||
|
);
|
||||||
|
if ($fnret && $fnret->value > time()) {
|
||||||
|
my $expiry = $fnret->value - time();
|
||||||
|
my $human = OVH::Bastion::duration2human(seconds => $expiry)->value;
|
||||||
|
$ret{'ingress_piv_grace'} = {
|
||||||
|
enabled => 1,
|
||||||
|
expiration_timestamp => $fnret->value,
|
||||||
|
seconds_remaining => $expiry,
|
||||||
|
expiration_date => $human->{'date'},
|
||||||
|
time_remaining => $human->{'duration'},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ret{'ingress_piv_grace'} = {enabled => 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret{'global_ingress_policy'} = !!OVH::Bastion::config('ingressRequirePIV')->value + 0;
|
||||||
|
|
||||||
|
$ret{'effective_ingress_piv_policy'} =
|
||||||
|
!!OVH::Bastion::is_effective_piv_account_policy_enabled(account => $account)->is_ok + 0;
|
||||||
|
|
||||||
|
$ret{'mfa_password_required'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_REQUIRED_GROUP) + 0;
|
||||||
|
$ret{'mfa_password_bypass'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_BYPASS_GROUP) + 0;
|
||||||
|
$ret{'mfa_password_configured'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_CONFIGURED_GROUP) + 0;
|
||||||
|
|
||||||
|
$ret{'mfa_totp_required'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_REQUIRED_GROUP) + 0;
|
||||||
|
$ret{'mfa_totp_bypass'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_BYPASS_GROUP) + 0;
|
||||||
|
$ret{'mfa_totp_configured'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_CONFIGURED_GROUP) + 0;
|
||||||
|
|
||||||
|
$ret{'pam_auth_bypass'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::PAM_AUTH_BYPASS_GROUP) + 0;
|
||||||
|
|
||||||
|
$ret{'pubkey_auth_optional'} =
|
||||||
|
!!OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::OSH_PUBKEY_AUTH_OPTIONAL_GROUP) + 0;
|
||||||
|
|
||||||
|
$ret{'personal_egress_mfa_required'} =
|
||||||
|
OVH::Bastion::account_config(account => $account, key => "personal_egress_mfa_required")->value;
|
||||||
|
$ret{'personal_egress_mfa_required'} ||= 'none'; # no config means no mfa
|
||||||
|
|
||||||
|
$ret{'idle_ignore'} = !!OVH::Bastion::account_config(
|
||||||
|
account => $account,
|
||||||
|
key => OVH::Bastion::OPT_ACCOUNT_IDLE_IGNORE,
|
||||||
|
public => 1
|
||||||
|
) + 0;
|
||||||
|
|
||||||
|
$ret{'max_inactive_days'} =
|
||||||
|
OVH::Bastion::account_config(account => $account, %{OVH::Bastion::OPT_ACCOUNT_MAX_INACTIVE_DAYS()})->value;
|
||||||
|
|
||||||
|
if ($withPasswordInfo) {
|
||||||
|
my @command = qw{ sudo -n -u root -- /usr/bin/env perl -T };
|
||||||
|
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountGetPasswordInfo';
|
||||||
|
push @command, '--account', $account;
|
||||||
|
$fnret = OVH::Bastion::helper(cmd => \@command);
|
||||||
|
if ($fnret) {
|
||||||
|
$ret{'password'}{$_} = $fnret->value->{$_} for (keys %{$fnret->value});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$return{$account} = \%ret;
|
||||||
|
|
||||||
|
# print all this in a human-readable format, except if we've been asked
|
||||||
|
# to dump the data for all accounts, in which case the caller will only use
|
||||||
|
# our JSON output
|
||||||
|
print_account_info(%ret) if !$all;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_account_info {
|
||||||
|
my %ret = @_;
|
||||||
|
|
||||||
|
my $acc = $ret{'account'};
|
||||||
|
osh_info("$acc is a bastion " . colored('admin', 'green')) if $ret{'is_admin'};
|
||||||
|
osh_info("$account is a bastion " . colored('superowner', 'green')) if $ret{'is_superowner'};
|
||||||
|
osh_info("$account is a bastion " . colored('auditor', 'green')) if $ret{'is_auditor'};
|
||||||
|
|
||||||
|
osh_info "This account has access to the following restricted commands:";
|
||||||
|
|
||||||
|
osh_info("- $_") for @{$ret{'allowed_commands'}};
|
||||||
|
osh_info "(none)" if (!@{$ret{'allowed_commands'}});
|
||||||
|
|
||||||
|
if ($withGroups) {
|
||||||
|
osh_info("\nThis account is part of the following groups:");
|
||||||
|
foreach my $groupName (sort keys %{$ret{'groups'}}) {
|
||||||
|
my @flags = @{$ret{'groups'}{$groupName}{'flags'}};
|
||||||
|
my $line = sprintf "%18s", $groupName;
|
||||||
$line .= sprintf " %14s", colored(grep({ $_ eq 'owner' } @flags) ? 'Owner' : '-', 'red');
|
$line .= sprintf " %14s", colored(grep({ $_ eq 'owner' } @flags) ? 'Owner' : '-', 'red');
|
||||||
$line .= sprintf " %19s", colored(grep({ $_ eq 'gatekeeper' } @flags) ? 'GateKeeper' : '-', 'yellow');
|
$line .= sprintf " %19s", colored(grep({ $_ eq 'gatekeeper' } @flags) ? 'GateKeeper' : '-', 'yellow');
|
||||||
$line .= sprintf " %18s", colored(grep({ $_ eq 'aclkeeper' } @flags) ? 'ACLKeeper' : '-', 'magenta');
|
$line .= sprintf " %18s", colored(grep({ $_ eq 'aclkeeper' } @flags) ? 'ACLKeeper' : '-', 'magenta');
|
||||||
$line .= sprintf " %15s", colored(grep({ $_ eq 'member' } @flags) ? 'Member' : '-', 'green');
|
$line .= sprintf " %15s", colored(grep({ $_ eq 'member' } @flags) ? 'Member' : '-', 'green');
|
||||||
$line .= sprintf " %14s", colored(grep({ $_ eq 'guest' } @flags) ? 'Guest' : '-', 'cyan');
|
$line .= sprintf " %14s", colored(grep({ $_ eq 'guest' } @flags) ? 'Guest' : '-', 'cyan');
|
||||||
osh_info $line;
|
osh_info($line);
|
||||||
$result_hash->{$name} = {flags => \@flags, name => $name};
|
|
||||||
}
|
}
|
||||||
|
osh_info("(none)") if not %{$ret{'groups'}};
|
||||||
|
osh_info("\n");
|
||||||
}
|
}
|
||||||
if (not keys %$result_hash) {
|
|
||||||
osh_info "(none)";
|
|
||||||
}
|
|
||||||
osh_info "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
$ret{'groups'} = $result_hash;
|
if ($ret{'always_active'}) {
|
||||||
|
osh_info("This account is " . colored('always', 'green') . " active");
|
||||||
my $canConnect = 1;
|
|
||||||
$ret{'always_active'} = OVH::Bastion::account_config(
|
|
||||||
account => $account,
|
|
||||||
key => OVH::Bastion::OPT_ACCOUNT_ALWAYS_ACTIVE,
|
|
||||||
public => 1
|
|
||||||
) ? 1 : 0;
|
|
||||||
if ($ret{'always_active'}) {
|
|
||||||
$ret{'is_active'} = 1;
|
|
||||||
osh_info "This account is " . colored('always', 'green') . " active";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$fnret = OVH::Bastion::is_account_active(account => $account);
|
|
||||||
if ($fnret->is_ok) {
|
|
||||||
osh_info "\nThis account is " . colored('active', 'green');
|
|
||||||
$ret{'is_active'} = 1;
|
|
||||||
}
|
}
|
||||||
elsif ($fnret->is_ko) {
|
elsif ($ret{'is_active'}) {
|
||||||
|
osh_info("This account is " . colored('active', 'green'));
|
||||||
|
}
|
||||||
|
else {
|
||||||
osh_info "\nThis account is " . colored('INACTIVE', 'red');
|
osh_info "\nThis account is " . colored('INACTIVE', 'red');
|
||||||
$canConnect = 0;
|
|
||||||
$ret{'is_active'} = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OVH::Bastion::is_auditor(account => $self)) {
|
|
||||||
|
|
||||||
# TTL check
|
|
||||||
|
|
||||||
$fnret = OVH::Bastion::is_account_ttl_nonexpired(sysaccount => $sysaccount, account => $account);
|
|
||||||
if ($fnret->is_ok && $fnret->err eq 'OK_NO_TTL') {
|
|
||||||
osh_info "This account has " . colored('no TTL set', 'green');
|
|
||||||
$ret{'is_ttl_expired'} = 0;
|
|
||||||
}
|
|
||||||
elsif ($fnret->is_ok && $fnret->err eq 'OK_TTL_VALID') {
|
|
||||||
osh_info "This account "
|
|
||||||
. colored('TTL is still valid', 'green')
|
|
||||||
. " (for "
|
|
||||||
. $fnret->value->{'details'}{'human'} . ")";
|
|
||||||
$ret{'is_ttl_expired'} = 0;
|
|
||||||
}
|
|
||||||
elsif ($fnret->is_ko) {
|
|
||||||
osh_info "This account "
|
|
||||||
. colored('TTL is EXPIRED', 'red')
|
|
||||||
. " (since "
|
|
||||||
. $fnret->value->{'details'}{'human'} . ")";
|
|
||||||
$canConnect = 0;
|
|
||||||
$ret{'is_ttl_expired'} = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
osh_warn "Error getting account TTL expiration info (" . $fnret->msg . ")";
|
|
||||||
}
|
|
||||||
$ret{'ttl_timestamp'} = ($fnret && $fnret->value) ? $fnret->value->{'expiry_time'} : undef;
|
|
||||||
|
|
||||||
# freeze check
|
|
||||||
|
|
||||||
$fnret = OVH::Bastion::is_account_nonfrozen(account => $account);
|
|
||||||
$ret{'is_frozen'} = undef;
|
|
||||||
if ($fnret->is_ok) {
|
|
||||||
osh_info "This account is " . colored('not frozen', 'green');
|
|
||||||
$ret{'is_frozen'} = 0;
|
|
||||||
}
|
|
||||||
elsif ($fnret->is_ko) {
|
|
||||||
my $freezeReason = $fnret->value->{'reason'} || 'no reason given';
|
|
||||||
osh_info "This account has been "
|
|
||||||
. colored('FROZEN', 'red') . " by "
|
|
||||||
. $fnret->value->{'by'}
|
|
||||||
. " ($freezeReason)";
|
|
||||||
$ret{'is_frozen'} = 1;
|
|
||||||
$ret{'freeze_info'} = $fnret->value;
|
|
||||||
$canConnect = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# expi check
|
if (defined $ret{'is_ttl_set'}) {
|
||||||
|
my $human = '?';
|
||||||
$fnret = OVH::Bastion::is_account_nonexpired(sysaccount => $sysaccount, remoteaccount => $remoteaccount);
|
if (!$ret{'is_ttl_set'}) {
|
||||||
if ($fnret->is_ok) {
|
osh_info("This account has " . colored('no TTL set', 'green'));
|
||||||
osh_info "This account has seen recent-enough activity to " . colored('not be activity-expired', 'green');
|
}
|
||||||
$ret{'is_expired'} = 0;
|
elsif (!$ret{'is_ttl_expired'}) {
|
||||||
}
|
$fnret = OVH::Bastion::duration2human(seconds => $ret{'ttl_timestamp'} - time());
|
||||||
elsif ($fnret->is_ko) {
|
$human = $fnret->value->{'human'};
|
||||||
osh_info "This account is "
|
osh_info "This account " . colored('TTL is still valid', 'green') . " (for $human)";
|
||||||
. colored('EXPIRED', 'red')
|
}
|
||||||
. " activity-wise (it hasn't been seen for a long time)";
|
else {
|
||||||
$canConnect = 0;
|
$fnret = OVH::Bastion::duration2human(seconds => time() - $ret{'ttl_timestamp'}, tense => "past");
|
||||||
$ret{'is_expired'} = 1;
|
$human = $fnret->value->{'human'};
|
||||||
}
|
osh_info "This account " . colored('TTL is EXPIRED', 'red') . " (since $human)";
|
||||||
else {
|
}
|
||||||
osh_warn "Error getting account expiration info (" . $fnret->msg . ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$fnret->is_err) {
|
if (defined $ret{'is_frozen'}) {
|
||||||
|
if (!$ret{'is_frozen'}) {
|
||||||
|
osh_info "This account is " . colored('not frozen', 'green');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my $freezeReason = $ret{'freeze_info'}{'reason'} || 'no reason given';
|
||||||
|
my $freezeBy = $ret{'freeze_info'}{'by'} || '(unknown)';
|
||||||
|
osh_info "This account has been " . colored('FROZEN', 'red') . " by $freezeBy ($freezeReason)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $ret{'is_expired'}) {
|
||||||
|
if ($ret{'is_expired'}) {
|
||||||
|
osh_info "This account is "
|
||||||
|
. colored('EXPIRED', 'red')
|
||||||
|
. " activity-wise (it hasn't been seen for a long time)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
osh_info "This account has seen recent-enough activity to " . colored('not be activity-expired', 'green');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $ret{'can_connect'}) {
|
||||||
osh_info "As a consequence, this account "
|
osh_info "As a consequence, this account "
|
||||||
. ($canConnect ? colored("can", 'green') : colored("CANNOT", 'red'))
|
. ($ret{'can_connect'} ? colored("can", 'green') : colored("CANNOT", 'red'))
|
||||||
. " connect to this bastion\n\n";
|
. " connect to this bastion\n\n";
|
||||||
$ret{'can_connect'} = $canConnect;
|
}
|
||||||
|
|
||||||
if ($fnret->value->{'already_seen_before'}) {
|
if (defined $ret{'already_seen_before'}) {
|
||||||
$ret{'already_seen_before'} = 1;
|
if ($ret{'already_seen_before'}) {
|
||||||
if (defined $fnret->value->{'seconds'}) {
|
if ($ret{'last_activity'}) {
|
||||||
$fnret = OVH::Bastion::duration2human(seconds => $fnret->value->{'seconds'}, tense => "past");
|
my $seenBeforeStr = $ret{'last_activity'}{'datetime_utc'};
|
||||||
if ($fnret) {
|
if ( $ret{'last_activity'}{'datetime_local'}
|
||||||
my $seenBeforeStr = $fnret->value->{'datetime_utc'};
|
&& $ret{'last_activity'}{'datetime_utc'} ne $ret{'last_activity'}{'datetime_local'})
|
||||||
if ( $fnret->value->{'datetime_local'}
|
{
|
||||||
&& $fnret->value->{'datetime_utc'} ne $fnret->value->{'datetime_local'})
|
$seenBeforeStr .= " / " . $ret{'last_activity'}{'datetime_local'};
|
||||||
{
|
|
||||||
$seenBeforeStr .= " / " . $fnret->value->{'datetime_local'};
|
|
||||||
}
|
|
||||||
$seenBeforeStr = sprintf(
|
|
||||||
"Last seen on %s (%s ago)",
|
|
||||||
colored($seenBeforeStr, 'magenta'),
|
|
||||||
$fnret->value->{'duration'},
|
|
||||||
);
|
|
||||||
osh_info $seenBeforeStr;
|
|
||||||
$ret{'last_activity'}{$_} = $fnret->value->{$_} for qw{ datetime_local datetime_utc };
|
|
||||||
$ret{'last_activity'}{'ago'} = $fnret->value->{'duration'};
|
|
||||||
$ret{'last_activity'}{'timestamp'} = time() - $fnret->value->{'seconds'};
|
|
||||||
}
|
}
|
||||||
|
$seenBeforeStr = sprintf("Last seen on %s (%s ago)", colored($seenBeforeStr, 'magenta'),
|
||||||
|
$ret{'last_activity'}{'ago'},);
|
||||||
|
osh_info($seenBeforeStr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
osh_info("This account has already been used at least once");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
osh_info "This account has " . colored('NEVER', 'red') . " been used (yet)";
|
osh_info("This account has " . colored('NEVER', 'red') . " been used (yet)");
|
||||||
$ret{'already_seen_before'} = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$fnret = OVH::Bastion::account_config(account => $account, key => "creation_info");
|
if ($ret{'creation_information'}) {
|
||||||
if ($fnret) {
|
if ($ret{'creation_information'}{'datetime_utc'}) {
|
||||||
my $creation_info;
|
my $createdOnStr = $ret{'creation_information'}{'datetime_utc'};
|
||||||
eval { $creation_info = decode_json($fnret->value); };
|
if ( $ret{'creation_information'}{'datetime_local'}
|
||||||
if ($@) {
|
&& $ret{'creation_information'}{'datetime_utc'} ne $ret{'creation_information'}{'datetime_local'})
|
||||||
osh_warn("While reading creation metadata information for account '$account', couldn't decode JSON: $@");
|
{
|
||||||
|
$createdOnStr .= " / " . $ret{'creation_information'}{'datetime_local'};
|
||||||
|
}
|
||||||
|
$createdOnStr = sprintf(
|
||||||
|
"Created on %s (%s ago)",
|
||||||
|
colored($createdOnStr, 'magenta'),
|
||||||
|
OVH::Bastion::duration2human(seconds => time() - $ret{'creation_information'}{'timestamp'})
|
||||||
|
->value->{'duration'}
|
||||||
|
);
|
||||||
|
osh_info($createdOnStr);
|
||||||
}
|
}
|
||||||
else {
|
if ($ret{'creation_information'}{'by'}) {
|
||||||
$ret{'creation_information'} = $creation_info;
|
osh_info("Created by " . colored($ret{'creation_information'}{'by'}, 'magenta'));
|
||||||
if ($creation_info->{'datetime_utc'}) {
|
}
|
||||||
my $createdOnStr = $creation_info->{'datetime_utc'};
|
if ($ret{'creation_information'}{'bastion_version'}) {
|
||||||
if ( $creation_info->{'datetime_local'}
|
osh_info("Created using The Bastion "
|
||||||
&& $creation_info->{'datetime_utc'} ne $creation_info->{'datetime_local'})
|
. colored('v' . $ret{'creation_information'}{'bastion_version'}, 'magenta'));
|
||||||
{
|
}
|
||||||
$createdOnStr .= " / " . $creation_info->{'datetime_local'};
|
if ($ret{'creation_information'}{'comment'}) {
|
||||||
}
|
osh_info(
|
||||||
$createdOnStr = sprintf(
|
"Creation with the following comment: " . colored($ret{'creation_information'}{'comment'}, 'magenta'));
|
||||||
"Created on %s (%s ago)",
|
|
||||||
colored($createdOnStr, 'magenta'),
|
|
||||||
OVH::Bastion::duration2human(seconds => time() - $creation_info->{'timestamp'})->value->{'duration'}
|
|
||||||
);
|
|
||||||
osh_info $createdOnStr;
|
|
||||||
}
|
|
||||||
if ($creation_info->{'by'}) {
|
|
||||||
osh_info "Created by " . colored($creation_info->{'by'}, 'magenta');
|
|
||||||
}
|
|
||||||
if ($creation_info->{'bastion_version'}) {
|
|
||||||
osh_info "Created using The Bastion " . colored('v' . $creation_info->{'bastion_version'}, 'magenta');
|
|
||||||
}
|
|
||||||
if ($creation_info->{'comment'}) {
|
|
||||||
osh_info "Creation with the following comment: " . colored($creation_info->{'comment'}, 'magenta');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
osh_info "\nAccount egress SSH config:";
|
osh_info "\nAccount egress SSH config:";
|
||||||
$fnret = OVH::Bastion::account_ssh_config_get(account => $account);
|
if ($ret{'account_egress_ssh_config'}{'type'} eq 'default') {
|
||||||
if ($fnret->err eq 'OK_EMPTY') {
|
|
||||||
osh_info "- (default)";
|
osh_info "- (default)";
|
||||||
$ret{'account_egress_ssh_config'}{'type'} = 'default';
|
|
||||||
}
|
}
|
||||||
elsif ($fnret->err eq 'ERR_FILE_LOCALLY_MODIFIED') {
|
elsif ($ret{'account_egress_ssh_config'}{'type'} eq 'locally_modified') {
|
||||||
osh_info "- (locally modified!)";
|
osh_info "- (locally modified!)";
|
||||||
$ret{'account_egress_ssh_config'}{'type'} = 'locally_modified';
|
|
||||||
}
|
}
|
||||||
elsif ($fnret) {
|
elsif ($ret{'account_egress_ssh_config'}{'type'} eq 'custom') {
|
||||||
$ret{'account_egress_ssh_config'}{'type'} = 'custom';
|
foreach my $key (sort keys %{$ret{'account_egress_ssh_config'}{'items'} || {}}) {
|
||||||
foreach my $key (sort keys %{$fnret->value}) {
|
osh_info "- $key " . $ret{'account_egress_ssh_config'}{'items'}{$key};
|
||||||
osh_info "- $key " . $fnret->value->{$key};
|
|
||||||
$ret{'account_egress_ssh_config'}{'items'}{$key} = $fnret->value->{$key};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$ret{'account_egress_ssh_config'}{'type'} = 'unknown';
|
osh_info "- (unknown)";
|
||||||
osh_info "- (unknown: " . $fnret . ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
osh_info "\nAccount PIV-only policy status:";
|
osh_info "\nAccount PIV-only policy status:";
|
||||||
$fnret = OVH::Bastion::account_config(
|
|
||||||
account => $account,
|
|
||||||
public => 1,
|
|
||||||
key => OVH::Bastion::OPT_ACCOUNT_INGRESS_PIV_POLICY
|
|
||||||
);
|
|
||||||
$ret{'ingress_piv_enforced'} = ($fnret && $fnret->value eq 'yes') ? 1 : 0; # keep for backwards compat
|
|
||||||
$ret{'ingress_piv_policy'} = $fnret->value || undef;
|
|
||||||
my $ingress_piv_policy_print = $ret{'ingress_piv_policy'} || 'default';
|
my $ingress_piv_policy_print = $ret{'ingress_piv_policy'} || 'default';
|
||||||
osh_info "- PIV policy for ingress keys on this account is set to "
|
osh_info "- PIV policy for ingress keys on this account is set to "
|
||||||
. colored($ingress_piv_policy_print, $ingress_piv_policy_print eq 'default' ? 'blue' : 'green');
|
. colored($ingress_piv_policy_print, $ingress_piv_policy_print eq 'default' ? 'blue' : 'green');
|
||||||
|
|
||||||
$fnret = OVH::Bastion::account_config(
|
if ($ret{'ingress_piv_grace'} && $ret{'ingress_piv_grace'}{'seconds_remaining'}) {
|
||||||
account => $account,
|
$fnret = OVH::Bastion::duration2human(seconds => $ret{'ingress_piv_grace'}{'seconds_remaining'})->value;
|
||||||
public => 1,
|
osh_info("- PIV grace period for this account is "
|
||||||
key => OVH::Bastion::OPT_ACCOUNT_INGRESS_PIV_GRACE
|
. colored('set', 'green')
|
||||||
);
|
. " and expires in "
|
||||||
if ($fnret && $fnret->value > time()) {
|
. $fnret->value->{'human'});
|
||||||
my $expiry = $fnret->value - time();
|
|
||||||
my $human = OVH::Bastion::duration2human(seconds => $expiry)->value;
|
|
||||||
osh_info "- PIV grace period for this account is "
|
|
||||||
. colored('set', 'green')
|
|
||||||
. " and expires in "
|
|
||||||
. $human->{'human'};
|
|
||||||
$ret{'ingress_piv_grace'} = {
|
|
||||||
enabled => 1,
|
|
||||||
expiration_timestamp => $fnret->value,
|
|
||||||
seconds_remaining => $expiry,
|
|
||||||
expiration_date => $human->{'date'},
|
|
||||||
time_remaining => $human->{'duration'},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
osh_info "- PIV grace period for this account is " . colored('inactive', 'blue');
|
osh_info "- PIV grace period for this account is " . colored('inactive', 'blue');
|
||||||
$ret{'ingress_piv_grace'} = {enabled => 0};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$fnret = OVH::Bastion::config('ingressRequirePIV')->value;
|
|
||||||
$ret{'global_ingress_policy'} = $fnret = OVH::Bastion::config('ingressRequirePIV')->value ? 1 : 0;
|
|
||||||
osh_info "- Global PIV policy status is "
|
osh_info "- Global PIV policy status is "
|
||||||
. ($ret{'global_ingress_policy'} ? colored('enabled', 'red') : colored('disabled', 'blue'));
|
. ($ret{'global_ingress_policy'} ? colored('enabled', 'red') : colored('disabled', 'blue'));
|
||||||
|
|
||||||
$fnret = OVH::Bastion::is_effective_piv_account_policy_enabled(account => $account);
|
|
||||||
$ret{'effective_ingress_piv_policy'} = $fnret->is_ok ? 1 : 0;
|
|
||||||
osh_info "- As a consequence, PIV policy is "
|
osh_info "- As a consequence, PIV policy is "
|
||||||
. ($ret{'effective_ingress_piv_policy'} ? colored('enforced', 'red') : colored('inactive', 'blue'))
|
. ($ret{'effective_ingress_piv_policy'} ? colored('enforced', 'red') : colored('inactive', 'blue'))
|
||||||
. " for this account";
|
. " for this account";
|
||||||
|
|
||||||
osh_info "\nAccount Multi-Factor Authentication status:";
|
osh_info "\nAccount Multi-Factor Authentication status:";
|
||||||
$ret{'mfa_password_required'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_REQUIRED_GROUP) ? 1 : 0;
|
|
||||||
$ret{'mfa_password_bypass'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_BYPASS_GROUP) ? 1 : 0;
|
|
||||||
$ret{'mfa_password_configured'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_CONFIGURED_GROUP) ? 1 : 0;
|
|
||||||
osh_info "- Additional password authentication is "
|
osh_info "- Additional password authentication is "
|
||||||
. ($ret{'mfa_password_required'} ? colored('required', 'green') : colored('not required', 'blue'))
|
. ($ret{'mfa_password_required'} ? colored('required', 'green') : colored('not required', 'blue'))
|
||||||
. " for this account";
|
. " for this account";
|
||||||
|
@ -347,12 +541,6 @@ if (OVH::Bastion::is_auditor(account => $self)) {
|
||||||
osh_info "- Additional password authentication is "
|
osh_info "- Additional password authentication is "
|
||||||
. ($ret{'mfa_password_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue'));
|
. ($ret{'mfa_password_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue'));
|
||||||
|
|
||||||
$ret{'mfa_totp_required'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_REQUIRED_GROUP) ? 1 : 0;
|
|
||||||
$ret{'mfa_totp_bypass'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_BYPASS_GROUP) ? 1 : 0;
|
|
||||||
$ret{'mfa_totp_configured'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_CONFIGURED_GROUP) ? 1 : 0;
|
|
||||||
osh_info "- Additional TOTP authentication is "
|
osh_info "- Additional TOTP authentication is "
|
||||||
. ($ret{'mfa_totp_required'} ? colored('required', 'green') : colored('not required', 'blue'))
|
. ($ret{'mfa_totp_required'} ? colored('required', 'green') : colored('not required', 'blue'))
|
||||||
. " for this account";
|
. " for this account";
|
||||||
|
@ -362,31 +550,18 @@ if (OVH::Bastion::is_auditor(account => $self)) {
|
||||||
osh_info "- Additional TOTP authentication is "
|
osh_info "- Additional TOTP authentication is "
|
||||||
. ($ret{'mfa_totp_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue'));
|
. ($ret{'mfa_totp_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue'));
|
||||||
|
|
||||||
$ret{'pam_auth_bypass'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::PAM_AUTH_BYPASS_GROUP) ? 1 : 0;
|
|
||||||
osh_info "- PAM authentication bypass is "
|
osh_info "- PAM authentication bypass is "
|
||||||
. ($ret{'pam_auth_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue'));
|
. ($ret{'pam_auth_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue'));
|
||||||
|
|
||||||
$ret{'pubkey_auth_optional'} =
|
|
||||||
OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::OSH_PUBKEY_AUTH_OPTIONAL_GROUP) ? 1 : 0;
|
|
||||||
osh_info "- Optional public key authentication is "
|
osh_info "- Optional public key authentication is "
|
||||||
. ($ret{'pubkey_auth_optional'} ? colored('enabled', 'green') : colored('disabled', 'blue'));
|
. ($ret{'pubkey_auth_optional'} ? colored('enabled', 'green') : colored('disabled', 'blue'));
|
||||||
|
|
||||||
$ret{'personal_egress_mfa_required'} =
|
|
||||||
OVH::Bastion::account_config(account => $account, key => "personal_egress_mfa_required")->value;
|
|
||||||
$ret{'personal_egress_mfa_required'} ||= 'none'; # no config means no mfa
|
|
||||||
osh_info "- MFA policy on personal accesses (using personal keys) on egress side is: "
|
osh_info "- MFA policy on personal accesses (using personal keys) on egress side is: "
|
||||||
. $ret{'personal_egress_mfa_required'};
|
. $ret{'personal_egress_mfa_required'};
|
||||||
|
|
||||||
$ret{'idle_ignore'} =
|
|
||||||
OVH::Bastion::account_config(account => $account, key => OVH::Bastion::OPT_ACCOUNT_IDLE_IGNORE, public => 1)
|
|
||||||
? 1
|
|
||||||
: 0;
|
|
||||||
osh_info "\n- Account is immune to idle counter-measures: "
|
osh_info "\n- Account is immune to idle counter-measures: "
|
||||||
. ($ret{'idle_ignore'} ? colored('yes', 'green') : colored('no', 'blue'));
|
. ($ret{'idle_ignore'} ? colored('yes', 'green') : colored('no', 'blue'));
|
||||||
|
|
||||||
$ret{'max_inactive_days'} =
|
|
||||||
OVH::Bastion::account_config(account => $account, %{OVH::Bastion::OPT_ACCOUNT_MAX_INACTIVE_DAYS()})->value;
|
|
||||||
if (!defined $ret{'max_inactive_days'}) {
|
if (!defined $ret{'max_inactive_days'}) {
|
||||||
osh_info "- Maximum number of days of inactivity before account is disabled: (default)";
|
osh_info "- Maximum number of days of inactivity before account is disabled: (default)";
|
||||||
}
|
}
|
||||||
|
@ -397,12 +572,7 @@ if (OVH::Bastion::is_auditor(account => $self)) {
|
||||||
osh_info "- Maximum number of days of inactivity before account is disabled: " . $ret{'max_inactive_days'};
|
osh_info "- Maximum number of days of inactivity before account is disabled: " . $ret{'max_inactive_days'};
|
||||||
}
|
}
|
||||||
|
|
||||||
my @command = qw{ sudo -n -u root -- /usr/bin/env perl -T };
|
if ($ret{'password'}) {
|
||||||
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountGetPasswordInfo';
|
|
||||||
push @command, '--account', $account;
|
|
||||||
$fnret = OVH::Bastion::helper(cmd => \@command);
|
|
||||||
if ($fnret) {
|
|
||||||
$ret{'password'}{$_} = $fnret->value->{$_} for (keys %{$fnret->value});
|
|
||||||
osh_info "Account PAM UNIX password information (used for password MFA):";
|
osh_info "Account PAM UNIX password information (used for password MFA):";
|
||||||
if ($ret{'password'}{'password'} eq 'locked') {
|
if ($ret{'password'}{'password'} eq 'locked') {
|
||||||
osh_info "- No valid password is set";
|
osh_info "- No valid password is set";
|
||||||
|
@ -432,6 +602,16 @@ if (OVH::Bastion::is_auditor(account => $self)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
osh_ok(\%ret);
|
if (!$all) {
|
||||||
|
# only one account, don't return a hash of hash to keep backward compat
|
||||||
|
my @keys = keys %return;
|
||||||
|
osh_ok $return{$keys[0]};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
osh_info "If you're only seeing this line, you might want to use --json";
|
||||||
|
osh_ok \%return;
|
||||||
|
}
|
||||||
|
|
|
@ -1,47 +1,71 @@
|
||||||
|
Usage examples
|
||||||
|
==============
|
||||||
|
|
||||||
|
Show info about a specific account::
|
||||||
|
|
||||||
|
--osh accountInfo --account jdoe12
|
||||||
|
|
||||||
|
Gather info about all accounts, with no extra data except their egress keys::
|
||||||
|
|
||||||
|
--osh accountInfo --all --without-everything --with-egress-keys --json
|
||||||
|
|
||||||
|
Gather info about all accounts, including all extra data (and possibly future options)::
|
||||||
|
|
||||||
|
--osh accountInfo --all --with-everything --json
|
||||||
|
|
||||||
Output example
|
Output example
|
||||||
==============
|
==============
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
~ user1 is a bastion admin
|
│ user1 is a bastion admin
|
||||||
~ user1 is a bastion superowner
|
│ user1 is a bastion superowner
|
||||||
~ user1 is a bastion auditor
|
│ user1 is a bastion auditor
|
||||||
~ user1 has access to the following restricted commands:
|
│
|
||||||
~ - accountCreate
|
│ user1 has access to the following restricted commands:
|
||||||
~ - accountDelete
|
│ - accountCreate
|
||||||
~ - groupCreate
|
│ - accountDelete
|
||||||
~ - groupDelete
|
│ - groupCreate
|
||||||
~
|
│ - groupDelete
|
||||||
~ This account is part of the following groups:
|
│
|
||||||
~ testgroup1 Owner GateKeeper ACLKeeper Member -
|
│ This account is part of the following groups:
|
||||||
~ gatekeeper-grp2 Owner GateKeeper - - -
|
│ testgroup1 Owner GateKeeper ACLKeeper Member -
|
||||||
~
|
│ gatekeeper-grp2 Owner GateKeeper - - -
|
||||||
~ This account is active
|
│
|
||||||
~ This account is not expired
|
│ This account is active
|
||||||
~ As a consequence, this account can connect to this bastion
|
│ This account has no TTL set
|
||||||
~
|
│ This account is not frozen
|
||||||
~ This account has already been used at least once
|
│ This account has seen recent-enough activity to not be activity-expired
|
||||||
~ Last seen on Wed 2020-07-15 12:06:27 UTC (00:00:00 ago)
|
│ As a consequence, this account can connect to this bastion
|
||||||
~
|
│
|
||||||
~ Account egress SSH config:
|
│ Last seen on Thu 2023-03-16 07:51:49 UTC (00:00:00 ago)
|
||||||
~ - (default)
|
│ Created on Fri 2022-06-17 09:52:50 UTC (271d+21:58:59 ago)
|
||||||
~
|
│ Created by jdoe
|
||||||
~ PIV-enforced policy for ingress keys on this account is enabled
|
│ Created using The Bastion v3.08.01
|
||||||
~
|
│
|
||||||
~ Account Multi-Factor Authentication status:
|
│ Account egress SSH config:
|
||||||
~ - Additional password authentication is not required for this account
|
│ - (default)
|
||||||
~ - Additional password authentication bypass is disabled for this account
|
│
|
||||||
~ - Additional password authentication is enabled and active
|
│ PIV-enforced policy for ingress keys on this account is enabled
|
||||||
~ - Additional TOTP authentication is not required for this account
|
│
|
||||||
~ - Additional TOTP authentication bypass is disabled for this account
|
│ Account Multi-Factor Authentication status:
|
||||||
~ - Additional TOTP authentication is disabled
|
│ - Additional password authentication is not required for this account
|
||||||
~ - PAM authentication bypass is disabled
|
│ - Additional password authentication bypass is disabled for this account
|
||||||
~ - Optional public key authentication is disabled
|
│ - Additional password authentication is enabled and active
|
||||||
~ - MFA policy on personal accesses (using personal keys) on egress side is: password
|
│ - Additional TOTP authentication is not required for this account
|
||||||
|
│ - Additional TOTP authentication bypass is disabled for this account
|
||||||
|
│ - Additional TOTP authentication is disabled
|
||||||
|
│ - PAM authentication bypass is disabled
|
||||||
|
│ - Optional public key authentication is disabled
|
||||||
|
│ - MFA policy on personal accesses (using personal keys) on egress side is: password
|
||||||
|
│
|
||||||
|
│ - Account is immune to idle counter-measures: no
|
||||||
|
│ - Maximum number of days of inactivity before account is disabled: (default)
|
||||||
|
│
|
||||||
|
│ Account PAM UNIX password information (used for password MFA):
|
||||||
|
│ - Password is set
|
||||||
|
│ - Password was last changed on 2023-01-27
|
||||||
|
│ - Password must be changed every 90 days at least
|
||||||
|
│ - A warning is displayed 75 days before expiration
|
||||||
|
│ - Account will not be disabled after password expiration
|
||||||
|
|
||||||
~ Account PAM UNIX password information (used for password MFA):
|
|
||||||
~ - Password is set
|
|
||||||
~ - Password was last changed on 2020-04-27
|
|
||||||
~ - Password must be changed every 90 days at least
|
|
||||||
~ - A warning is displayed 75 days before expiration
|
|
||||||
~ - Account will not be disabled after password expiration
|
|
||||||
|
|
|
@ -1,18 +1,33 @@
|
||||||
|
Usage examples
|
||||||
|
==============
|
||||||
|
|
||||||
|
Show info about a specific group::
|
||||||
|
|
||||||
|
--osh groupInfo --group mygroup2
|
||||||
|
|
||||||
|
Gather info about all groups, with no extra data except their keys::
|
||||||
|
|
||||||
|
--osh groupInfo --all --without-everything --with-keys --json
|
||||||
|
|
||||||
|
Gather info about all groups, including all extra data (and possibly future options)::
|
||||||
|
|
||||||
|
--osh groupInfo --all --with-everything --json
|
||||||
|
|
||||||
Output example
|
Output example
|
||||||
==============
|
==============
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
~ Group mygroup's Owners are: user1
|
| Group mygroup's Owners are: user1
|
||||||
~ Group mygroup's GateKeepers (managing the members/guests list) are: user2
|
| Group mygroup's GateKeepers (managing the members/guests list) are: user2
|
||||||
~ Group mygroup's ACLKeepers (managing the group servers list) are: user3
|
| Group mygroup's ACLKeepers (managing the group servers list) are: user3
|
||||||
~ Group mygroup's Members (with access to ALL the group servers) are: user4
|
| Group mygroup's Members (with access to ALL the group servers) are: user4
|
||||||
~ Group mygroup's Guests (with access to SOME of the group servers) are: user5
|
| Group mygroup's Guests (with access to SOME of the group servers) are: user5
|
||||||
~
|
|
|
||||||
~ The public key of this group is:
|
| The public key of this group is:
|
||||||
~
|
|
|
||||||
~ fingerprint: SHA256:r/PQS4wLdSWqjYsDca8ReKjhq0l9EX+zQgiUR5qKdlc (ED25519-256) [2018/04/16]
|
| fingerprint: SHA256:r/PQS4wLdSWqjYsDca8ReKjhq0l9EX+zQgiUR5qKdlc (ED25519-256) [2018/04/16]
|
||||||
~ keyline follows, please copy the *whole* line:
|
| keyline follows, please copy the *whole* line:
|
||||||
from="203.0.113.4/32,192.0.2.0/26" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdD60bA3NgaOpRLgcACWfKcAMRQQRyFMppwp5GpHLTB mygroup@testbastion:1523886640
|
from="203.0.113.4/32,192.0.2.0/26" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdD60bA3NgaOpRLgcACWfKcAMRQQRyFMppwp5GpHLTB mygroup@testbastion:1523886640
|
||||||
|
|
||||||
The first paragraph of the output lists the different roles along with the people having these roles.
|
The first paragraph of the output lists the different roles along with the people having these roles.
|
||||||
|
|
|
@ -44,7 +44,7 @@ do
|
||||||
else
|
else
|
||||||
perl "$pluginfile" '' '' '' '' | perl -e 'undef $/; $_=<>; s/\n+$/\n/; print $_' | perl -ne '
|
perl "$pluginfile" '' '' '' '' | perl -e 'undef $/; $_=<>; s/\n+$/\n/; print $_' | perl -ne '
|
||||||
if (m{^Usage: (.+)}) { print ".. admonition:: usage\n :class: cmdusage\n\n $1\n\n.. program:: '"$name"'\n\n"; }
|
if (m{^Usage: (.+)}) { print ".. admonition:: usage\n :class: cmdusage\n\n $1\n\n.. program:: '"$name"'\n\n"; }
|
||||||
elsif (m{^ (-[- ,a-z|/A-Z"'"'"']+) (.+)}) { ($c,$t)=($1,$2); $c=~s/ +$//; print ".. option:: $c\n\n $t\n\n"; }
|
elsif (m{^ (-[- ,a-z|/A-Z"'"'"'\[\]]+) (.+)}) { ($c,$t)=($1,$2); $c=~s/ +$//; print ".. option:: $c\n\n $t\n\n"; }
|
||||||
elsif ($l++ == 0) { chomp; print "$_\n"."="x(length($_))."\n\n"; }
|
elsif ($l++ == 0) { chomp; print "$_\n"."="x(length($_))."\n\n"; }
|
||||||
else { print "$_"; }
|
else { print "$_"; }
|
||||||
'
|
'
|
||||||
|
|
|
@ -62,6 +62,9 @@ source_suffix = '.rst'
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# Default syntax highlighting language.
|
||||||
|
highlight_language = 'shell'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
#
|
#
|
||||||
|
|
|
@ -9,30 +9,58 @@ Print some basic information about a group
|
||||||
.. admonition:: usage
|
.. admonition:: usage
|
||||||
:class: cmdusage
|
:class: cmdusage
|
||||||
|
|
||||||
--osh groupInfo --group GROUP
|
--osh groupInfo <--group GROUP|--all> [OPTIONS]
|
||||||
|
|
||||||
.. program:: groupInfo
|
.. program:: groupInfo
|
||||||
|
|
||||||
|
|
||||||
.. option:: --group GROUP
|
.. option:: --group GROUP
|
||||||
|
|
||||||
specify the group to display the infos of
|
Specify the group to display the info of
|
||||||
|
|
||||||
|
.. option:: --all
|
||||||
|
|
||||||
|
Dump info for all groups (auditors only), use with ``--json``
|
||||||
|
|
||||||
|
|
||||||
|
.. option:: --with[out]-everything
|
||||||
|
|
||||||
|
Include or exclude all below options, including future ones
|
||||||
|
|
||||||
|
.. option:: --with[out]-keys
|
||||||
|
|
||||||
|
Whether to include the group keys list (slow-ish, default: yes)
|
||||||
|
|
||||||
|
Usage examples
|
||||||
|
==============
|
||||||
|
|
||||||
|
Show info about a specific group::
|
||||||
|
|
||||||
|
--osh groupInfo --group mygroup2
|
||||||
|
|
||||||
|
Gather info about all groups, with no extra data except their keys::
|
||||||
|
|
||||||
|
--osh groupInfo --all --without-everything --with-keys --json
|
||||||
|
|
||||||
|
Gather info about all groups, including all extra data (and possibly future options)::
|
||||||
|
|
||||||
|
--osh groupInfo --all --with-everything --json
|
||||||
|
|
||||||
Output example
|
Output example
|
||||||
==============
|
==============
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
~ Group mygroup's Owners are: user1
|
| Group mygroup's Owners are: user1
|
||||||
~ Group mygroup's GateKeepers (managing the members/guests list) are: user2
|
| Group mygroup's GateKeepers (managing the members/guests list) are: user2
|
||||||
~ Group mygroup's ACLKeepers (managing the group servers list) are: user3
|
| Group mygroup's ACLKeepers (managing the group servers list) are: user3
|
||||||
~ Group mygroup's Members (with access to ALL the group servers) are: user4
|
| Group mygroup's Members (with access to ALL the group servers) are: user4
|
||||||
~ Group mygroup's Guests (with access to SOME of the group servers) are: user5
|
| Group mygroup's Guests (with access to SOME of the group servers) are: user5
|
||||||
~
|
|
|
||||||
~ The public key of this group is:
|
| The public key of this group is:
|
||||||
~
|
|
|
||||||
~ fingerprint: SHA256:r/PQS4wLdSWqjYsDca8ReKjhq0l9EX+zQgiUR5qKdlc (ED25519-256) [2018/04/16]
|
| fingerprint: SHA256:r/PQS4wLdSWqjYsDca8ReKjhq0l9EX+zQgiUR5qKdlc (ED25519-256) [2018/04/16]
|
||||||
~ keyline follows, please copy the *whole* line:
|
| keyline follows, please copy the *whole* line:
|
||||||
from="203.0.113.4/32,192.0.2.0/26" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdD60bA3NgaOpRLgcACWfKcAMRQQRyFMppwp5GpHLTB mygroup@testbastion:1523886640
|
from="203.0.113.4/32,192.0.2.0/26" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdD60bA3NgaOpRLgcACWfKcAMRQQRyFMppwp5GpHLTB mygroup@testbastion:1523886640
|
||||||
|
|
||||||
The first paragraph of the output lists the different roles along with the people having these roles.
|
The first paragraph of the output lists the different roles along with the people having these roles.
|
||||||
|
|
|
@ -9,7 +9,7 @@ Display some information about an account
|
||||||
.. admonition:: usage
|
.. admonition:: usage
|
||||||
:class: cmdusage
|
:class: cmdusage
|
||||||
|
|
||||||
--osh accountInfo --account ACCOUNT [--list-groups]
|
--osh accountInfo <--account ACCOUNT|--all> [OPTIONS]
|
||||||
|
|
||||||
.. program:: accountInfo
|
.. program:: accountInfo
|
||||||
|
|
||||||
|
@ -18,54 +18,95 @@ Display some information about an account
|
||||||
|
|
||||||
The account name to work on
|
The account name to work on
|
||||||
|
|
||||||
.. option:: --list-groups
|
.. option:: --all
|
||||||
|
|
||||||
Show which groups the account has a role on
|
Dump info for all accounts (auditors only), use with ``--json``
|
||||||
|
|
||||||
|
|
||||||
|
.. option:: --with[out]-everything
|
||||||
|
|
||||||
|
Include or exclude all below options, including future ones
|
||||||
|
|
||||||
|
.. option:: --with[out]-groups
|
||||||
|
|
||||||
|
Whether to include the groups the account has a role on (SLOW, default: no)
|
||||||
|
|
||||||
|
.. option:: --with[out]-mfa-password-info
|
||||||
|
|
||||||
|
Whether to include MFA password info of the account (SLOW, auditors only, default: no)
|
||||||
|
|
||||||
|
.. option:: --with[out]-egress-keys
|
||||||
|
|
||||||
|
Whether to include the account's egress keys (SLOW, auditors only, default: no)
|
||||||
|
|
||||||
|
Usage examples
|
||||||
|
==============
|
||||||
|
|
||||||
|
Show info about a specific account::
|
||||||
|
|
||||||
|
--osh accountInfo --account jdoe12
|
||||||
|
|
||||||
|
Gather info about all accounts, with no extra data except their egress keys::
|
||||||
|
|
||||||
|
--osh accountInfo --all --without-everything --with-egress-keys --json
|
||||||
|
|
||||||
|
Gather info about all accounts, including all extra data (and possibly future options)::
|
||||||
|
|
||||||
|
--osh accountInfo --all --with-everything --json
|
||||||
|
|
||||||
Output example
|
Output example
|
||||||
==============
|
==============
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
~ user1 is a bastion admin
|
│ user1 is a bastion admin
|
||||||
~ user1 is a bastion superowner
|
│ user1 is a bastion superowner
|
||||||
~ user1 is a bastion auditor
|
│ user1 is a bastion auditor
|
||||||
~ user1 has access to the following restricted commands:
|
│
|
||||||
~ - accountCreate
|
│ user1 has access to the following restricted commands:
|
||||||
~ - accountDelete
|
│ - accountCreate
|
||||||
~ - groupCreate
|
│ - accountDelete
|
||||||
~ - groupDelete
|
│ - groupCreate
|
||||||
~
|
│ - groupDelete
|
||||||
~ This account is part of the following groups:
|
│
|
||||||
~ testgroup1 Owner GateKeeper ACLKeeper Member -
|
│ This account is part of the following groups:
|
||||||
~ gatekeeper-grp2 Owner GateKeeper - - -
|
│ testgroup1 Owner GateKeeper ACLKeeper Member -
|
||||||
~
|
│ gatekeeper-grp2 Owner GateKeeper - - -
|
||||||
~ This account is active
|
│
|
||||||
~ This account is not expired
|
│ This account is active
|
||||||
~ As a consequence, this account can connect to this bastion
|
│ This account has no TTL set
|
||||||
~
|
│ This account is not frozen
|
||||||
~ This account has already been used at least once
|
│ This account has seen recent-enough activity to not be activity-expired
|
||||||
~ Last seen on Wed 2020-07-15 12:06:27 UTC (00:00:00 ago)
|
│ As a consequence, this account can connect to this bastion
|
||||||
~
|
│
|
||||||
~ Account egress SSH config:
|
│ Last seen on Thu 2023-03-16 07:51:49 UTC (00:00:00 ago)
|
||||||
~ - (default)
|
│ Created on Fri 2022-06-17 09:52:50 UTC (271d+21:58:59 ago)
|
||||||
~
|
│ Created by jdoe
|
||||||
~ PIV-enforced policy for ingress keys on this account is enabled
|
│ Created using The Bastion v3.08.01
|
||||||
~
|
│
|
||||||
~ Account Multi-Factor Authentication status:
|
│ Account egress SSH config:
|
||||||
~ - Additional password authentication is not required for this account
|
│ - (default)
|
||||||
~ - Additional password authentication bypass is disabled for this account
|
│
|
||||||
~ - Additional password authentication is enabled and active
|
│ PIV-enforced policy for ingress keys on this account is enabled
|
||||||
~ - Additional TOTP authentication is not required for this account
|
│
|
||||||
~ - Additional TOTP authentication bypass is disabled for this account
|
│ Account Multi-Factor Authentication status:
|
||||||
~ - Additional TOTP authentication is disabled
|
│ - Additional password authentication is not required for this account
|
||||||
~ - PAM authentication bypass is disabled
|
│ - Additional password authentication bypass is disabled for this account
|
||||||
~ - Optional public key authentication is disabled
|
│ - Additional password authentication is enabled and active
|
||||||
~ - MFA policy on personal accesses (using personal keys) on egress side is: password
|
│ - Additional TOTP authentication is not required for this account
|
||||||
|
│ - Additional TOTP authentication bypass is disabled for this account
|
||||||
|
│ - Additional TOTP authentication is disabled
|
||||||
|
│ - PAM authentication bypass is disabled
|
||||||
|
│ - Optional public key authentication is disabled
|
||||||
|
│ - MFA policy on personal accesses (using personal keys) on egress side is: password
|
||||||
|
│
|
||||||
|
│ - Account is immune to idle counter-measures: no
|
||||||
|
│ - Maximum number of days of inactivity before account is disabled: (default)
|
||||||
|
│
|
||||||
|
│ Account PAM UNIX password information (used for password MFA):
|
||||||
|
│ - Password is set
|
||||||
|
│ - Password was last changed on 2023-01-27
|
||||||
|
│ - Password must be changed every 90 days at least
|
||||||
|
│ - A warning is displayed 75 days before expiration
|
||||||
|
│ - Account will not be disabled after password expiration
|
||||||
|
|
||||||
~ Account PAM UNIX password information (used for password MFA):
|
|
||||||
~ - Password is set
|
|
||||||
~ - Password was last changed on 2020-04-27
|
|
||||||
~ - Password must be changed every 90 days at least
|
|
||||||
~ - A warning is displayed 75 days before expiration
|
|
||||||
~ - Account will not be disabled after password expiration
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ The main takeaways are:
|
||||||
Global policy
|
Global policy
|
||||||
=============
|
=============
|
||||||
|
|
||||||
If you want to apply a policy bastion-wide, please refer to the :ref:`ingressRequiresPIV` option.
|
If you want to apply a policy bastion-wide, please refer to the :ref:`ingressRequirePIV` option.
|
||||||
This policy can still be overridden per-account if needed, see above.
|
This policy can still be overridden per-account if needed, see above.
|
||||||
|
|
||||||
Temporary grace period
|
Temporary grace period
|
||||||
|
|
|
@ -45,10 +45,10 @@ testsuite_accountinfo()
|
||||||
|
|
||||||
# a0 should see basic info about a2
|
# a0 should see basic info about a2
|
||||||
success a0_accountinfo_a2_basic $a0 --osh accountInfo --account $account2
|
success a0_accountinfo_a2_basic $a0 --osh accountInfo --account $account2
|
||||||
json_document '{"error_message":"OK","command":"accountInfo","error_code":"OK","value":{"always_active":1,"is_active":1,"allowed_commands":[],"groups":{}}}'
|
json_document '{"error_message":"OK","command":"accountInfo","error_code":"OK","value":{"account":"'"$account2"'","always_active":1,"is_active":1,"allowed_commands":[],"groups":{}}}'
|
||||||
|
|
||||||
# a1 should see detailed info about a2
|
# a1 should see detailed info about a2
|
||||||
success a1_accountinfo_a2_detailed $a1 --osh accountInfo --account $account2
|
success a1_accountinfo_a2_detailed $a1 --osh accountInfo --account $account2 --with-mfa-password-info
|
||||||
json .error_code OK .command accountInfo .value.always_active 1 .value.is_active 1 .value.allowed_commands "[]"
|
json .error_code OK .command accountInfo .value.always_active 1 .value.is_active 1 .value.allowed_commands "[]"
|
||||||
json .value.ingress_piv_policy null .value.personal_egress_mfa_required none .value.pam_auth_bypass 0
|
json .value.ingress_piv_policy null .value.personal_egress_mfa_required none .value.pam_auth_bypass 0
|
||||||
json .value.password.min_days 0 .value.password.user "$account2" .value.password.password locked
|
json .value.password.min_days 0 .value.password.user "$account2" .value.password.password locked
|
||||||
|
@ -114,10 +114,29 @@ testsuite_accountinfo()
|
||||||
revoke accountCreate
|
revoke accountCreate
|
||||||
|
|
||||||
grant auditor
|
grant auditor
|
||||||
|
|
||||||
success a0_accountinfo_a4_max_inactive_days $a0 --osh accountInfo --account $account4
|
success a0_accountinfo_a4_max_inactive_days $a0 --osh accountInfo --account $account4
|
||||||
json .value.max_inactive_days 42
|
json .value.max_inactive_days 42
|
||||||
|
|
||||||
|
# take the opportunity to test --all
|
||||||
|
success a0_accountinfo_all $a0 --osh accountInfo --all
|
||||||
|
json $(cat <<EOS
|
||||||
|
.command accountInfo
|
||||||
|
.error_code OK
|
||||||
|
.value|length 6
|
||||||
|
.value["$account4"].creation_information.by $account0
|
||||||
|
.value["$account4"].personal_egress_mfa_required none
|
||||||
|
.value["healthcheck"].allowed_commands|length 0
|
||||||
|
.value["$account0"].max_inactive_days null
|
||||||
|
EOS
|
||||||
|
)
|
||||||
|
|
||||||
revoke auditor
|
revoke auditor
|
||||||
|
|
||||||
|
# --all should no longer work
|
||||||
|
plgfail a0_accountinfo_all_no_auditor $a0 --osh accountInfo --all
|
||||||
|
json .command accountInfo .error_code ERR_ACCESS_DENIED .value null
|
||||||
|
|
||||||
revoke accountModify
|
revoke accountModify
|
||||||
|
|
||||||
# sleep to ensure TTL has expired. add 2 seconds to be extra-sure and avoid int-rounding errors
|
# sleep to ensure TTL has expired. add 2 seconds to be extra-sure and avoid int-rounding errors
|
||||||
|
|
|
@ -500,6 +500,32 @@ EOS
|
||||||
|
|
||||||
# new state: g1[a1(ow,gk,acl,member) a2(acl)] g3[a0,a2,a3(ow,gk,acl,member)]
|
# new state: g1[a1(ow,gk,acl,member) a2(acl)] g3[a0,a2,a3(ow,gk,acl,member)]
|
||||||
|
|
||||||
|
# --all requires auditor rights
|
||||||
|
plgfail a0_groupInfo_all_not_auditor $a0 --osh groupInfo --all
|
||||||
|
json .command groupInfo .error_code ERR_ACCESS_DENIED .value null
|
||||||
|
|
||||||
|
grant auditor
|
||||||
|
|
||||||
|
success a0_groupInfo_all $a0 --osh groupInfo --all
|
||||||
|
json $(cat <<EOS
|
||||||
|
.command groupInfo
|
||||||
|
.error_code OK
|
||||||
|
.value|length 2
|
||||||
|
.value["$group1"].aclkeepers[0] $account1
|
||||||
|
.value["$group1"].aclkeepers[1] $account2
|
||||||
|
.value["$group1"].gatekeepers[0] $account1
|
||||||
|
.value["$group1"].members[0] $account1
|
||||||
|
.value["$group1"].owners[0] $account1
|
||||||
|
.value["$group1"].guests|length 0
|
||||||
|
.value["$group1"].keys|.[]|.family RSA
|
||||||
|
.value["$group3"].owners[0] $account0
|
||||||
|
.value["$group3"].owners[1] $account3
|
||||||
|
.value["$group3"].owners[2] $account2
|
||||||
|
EOS
|
||||||
|
)
|
||||||
|
|
||||||
|
revoke auditor
|
||||||
|
|
||||||
# then check that owner/gatekeeper commands still don't work
|
# then check that owner/gatekeeper commands still don't work
|
||||||
|
|
||||||
plgfail a2_fail_add_a3_as_g1_owner $a2 --osh groupAddOwner --group $group1 --account $account3
|
plgfail a2_fail_add_a3_as_g1_owner $a2 --osh groupAddOwner --group $group1 --account $account3
|
||||||
|
|
Loading…
Add table
Reference in a new issue