* Added support to specify @group values for the --targets parameter and in the targets file(s)

* Added support for nested aliases: up to 5 levels deep instead of just
one level
* Added --resolve-alias/--alias command-line parameter to manually check
the resolution any alias
* Fixed propagation of --debug flag (to clients & slaves)
* Fixed propagation of --create-dir flag (to clients & slaves)
* Fixed problem in --fix-local routine (by adding optional --fix-user
command-line parameter and code)
* Fixed check when adding key to ssh-agent
* Added checking on alias resolution in --check-syntax routine
* Better trap setting
* Added typeset-ing to vars
* Switched version numbering (now date based)
* Code cleanup (now error & warning free in shellcheck/perlcritic
linters)
This commit is contained in:
patvdv 2018-11-03 16:35:36 +01:00
parent af8bf6a665
commit a99decbf95
3 changed files with 710 additions and 296 deletions

View File

@ -13,7 +13,7 @@
# (leave blank for current user) # (leave blank for current user)
SSH_TRANSFER_USER="" SSH_TRANSFER_USER=""
# name of the OS group that should own the SSH controls files # name of the UNIX group that should own the SSH controls files (must exist already)
SSH_OWNER_GROUP="sshadmin" SSH_OWNER_GROUP="sshadmin"
# whether a 'chmod' needs to be executed after each sftp transfer [0=No; 1=Yes] # whether a 'chmod' needs to be executed after each sftp transfer [0=No; 1=Yes]
@ -42,7 +42,7 @@ SSH_UPDATE_OPTS="--verbose --remove"
# path to the ssh-keyscan too # path to the ssh-keyscan too
SSH_KEYSCAN_BIN="/usr/bin/ssh-keyscan" SSH_KEYSCAN_BIN="/usr/bin/ssh-keyscan"
# extra arguments/options for the ssh-keyscan command # extra arguments/options for the ssh-keyscan command
# by default -f <file> is used by manage_sudo.sh to supply hostnames, do not add here # by default -f <file> is used by manage_sudo.sh to supply hostnames, do not add here
SSH_KEYSCAN_ARGS="-t rsa" SSH_KEYSCAN_ARGS="-t rsa"

File diff suppressed because it is too large Load Diff

View File

@ -41,23 +41,25 @@ use Pod::Usage;
#****************************************************************************** #******************************************************************************
# ------------------------- CONFIGURATION starts here ------------------------- # ------------------------- CONFIGURATION starts here -------------------------
# define the V.R.F (version/release/fix) # define the version (YYYY-MM-DD)
my $MY_VRF = "1.2.0"; my $script_version = "2018-11-03";
# name of global configuration file (no path, must be located in the script directory) # name of global configuration file (no path, must be located in the script directory)
my $global_config_file = "update_ssh.conf"; my $global_config_file = "update_ssh.conf";
# name of localized configuration file (no path, must be located in the script directory) # name of localized configuration file (no path, must be located in the script directory)
my $local_config_file = "update_ssh.conf.local"; my $local_config_file = "update_ssh.conf.local";
# maxiumum level of recursion for alias resolution
my $max_recursion = 5;
# selinux context labels of key files for different RHEL version # selinux context labels of key files for different RHEL version
my %selinux_contexts = ( '5' => 'sshd_key_t', my %selinux_contexts = ( '5' => 'sshd_key_t',
'6' => 'ssh_home_t', '6' => 'ssh_home_t',
'7' => 'ssh_home_t'); '7' => 'ssh_home_t');
# ------------------------- CONFIGURATION ends here --------------------------- # ------------------------- CONFIGURATION ends here ---------------------------
# initialize variables # initialize variables
my ($debug, $verbose, $preview, $remove, $global, $use_fqdn) = (0,0,0,0,0,0); my ($debug, $verbose, $preview, $remove, $global, $use_fqdn) = (0,0,0,0,0,0);
my (@config_files, @zombie_files, $access_dir, $blacklist_file); my (@config_files, @zombie_files, $access_dir, $blacklist_file);
my (%options, @uname, @pwgetent, @accounts, %aliases, %keys, %access, @blacklist); my (%options, @uname, @pwgetent, @accounts, %aliases, %keys, %access, @blacklist);
my ($os, $hostname, $run_dir); my ($os, $hostname, $run_dir);
my ($selinux_status, $selinux_context, $linux_version, $has_selinux) = ("","","",0); my ($selinux_status, $selinux_context, $linux_version, $has_selinux, $recursion_count) = ("","","",0,1);
$|++; $|++;
@ -67,7 +69,7 @@ $|++;
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
sub do_log { sub do_log {
my $message = shift; my $message = shift;
if ($message =~ /^ERROR:/ || $message =~ /^WARN:/) { if ($message =~ /^ERROR:/ || $message =~ /^WARN:/) {
@ -118,7 +120,7 @@ sub parse_config_file {
} }
} }
} }
return (1); return (1);
} }
@ -132,7 +134,7 @@ sub resolve_aliases
foreach $entry (@tmp_array) { foreach $entry (@tmp_array) {
if ($entry =~ /^\@/) { if ($entry =~ /^\@/) {
($aliases{$entry}) ($aliases{$entry})
? push (@new_array, @{$aliases{$entry}}) ? push (@new_array, @{$aliases{$entry}})
: do_log ("WARN: unable to resolve alias $entry [$hostname]"); : do_log ("WARN: unable to resolve alias $entry [$hostname]");
} else { } else {
($entry) ($entry)
@ -147,14 +149,14 @@ sub resolve_aliases
sub set_file { sub set_file {
my ($file, $perm, $uid, $gid) = @_; my ($file, $perm, $uid, $gid) = @_;
chmod ($perm, "$file") chmod ($perm, "$file")
or do_log ("ERROR: cannot set permissions on $file [$! $hostname]") or do_log ("ERROR: cannot set permissions on $file [$! $hostname]")
and exit (1); and exit (1);
chown ($uid, $gid, "$file") chown ($uid, $gid, "$file")
or do_log ("ERROR: cannot set ownerships on $file [$! $hostname]") or do_log ("ERROR: cannot set ownerships on $file [$! $hostname]")
and exit (1); and exit (1);
return (1); return (1);
} }
@ -180,12 +182,12 @@ if ( @ARGV > 0 ) {
version|V version|V
)) || pod2usage(-verbose => 0); )) || pod2usage(-verbose => 0);
} }
pod2usage(-verbose => 0) unless (%options); pod2usage(-verbose => 0) unless (%options);
# check version parameter # check version parameter
if ($options{'version'}) { if ($options{'version'}) {
$verbose = 1; $verbose = 1;
do_log ("INFO: $0: version $MY_VRF"); do_log ("INFO: $0: version $script_version");
exit (0); exit (0);
} }
# check help parameter # check help parameter
@ -202,7 +204,7 @@ if ($options{'preview'}) {
$preview = 1; $preview = 1;
$verbose = 1; $verbose = 1;
if ($global) { if ($global) {
do_log ("INFO: running in GLOBAL PREVIEW mode"); do_log ("INFO: running in GLOBAL PREVIEW mode");
} else { } else {
do_log ("INFO: running in PREVIEW mode"); do_log ("INFO: running in PREVIEW mode");
} }
@ -234,7 +236,7 @@ do_log ("INFO: parsing configuration file(s) ...");
push (@config_files, "$run_dir/$global_config_file") if (-f "$run_dir/$global_config_file"); push (@config_files, "$run_dir/$global_config_file") if (-f "$run_dir/$global_config_file");
push (@config_files, "$run_dir/$local_config_file") if (-f "$run_dir/$local_config_file"); push (@config_files, "$run_dir/$local_config_file") if (-f "$run_dir/$local_config_file");
unless (@config_files) { unless (@config_files) {
do_log ("ERROR: unable to find any configuration file, bailing out [$hostname]") do_log ("ERROR: unable to find any configuration file, bailing out [$hostname]")
and exit (1); and exit (1);
} }
@ -249,7 +251,7 @@ unless ($preview and $global) {
if (-d $access_dir) { if (-d $access_dir) {
do_log ("INFO: host is under SSH control via $access_dir"); do_log ("INFO: host is under SSH control via $access_dir");
} else { } else {
do_log ("ERROR: host is not under SSH keys only control [$hostname]") do_log ("ERROR: host is not under SSH keys only control [$hostname]")
and exit (1); and exit (1);
} }
} }
@ -259,14 +261,14 @@ unless ($preview and $global) {
do_log ("INFO: checking for keys blacklist file ..."); do_log ("INFO: checking for keys blacklist file ...");
if (-f $blacklist_file) { if (-f $blacklist_file) {
open (BLACKLIST, "<", $blacklist_file) or \ open (BLACKLIST, "<", $blacklist_file) or \
do_log ("ERROR: cannot read keys blacklist file [$! $hostname]") do_log ("ERROR: cannot read keys blacklist file [$! $hostname]")
and exit (1); and exit (1);
@blacklist = <BLACKLIST>; @blacklist = <BLACKLIST>;
close (BLACKLIST); close (BLACKLIST);
do_log ("INFO: keys blacklist file found with ".scalar (@blacklist)." entr(y|ies) on $hostname"); do_log ("INFO: keys blacklist file found with ".scalar (@blacklist)." entr(y|ies) on $hostname");
print Dumper (\@blacklist) if $debug; print Dumper (\@blacklist) if $debug;
} else { } else {
do_log ("WARN: no keys blacklist file found [$hostname]"); do_log ("WARN: no keys blacklist file found [$hostname]");
} }
} }
@ -276,7 +278,7 @@ $os = $uname[0];
# who am I? # who am I?
unless ($preview and $global) { unless ($preview and $global) {
if ($< != 0) { if ($< != 0) {
do_log ("ERROR: script must be invoked as user 'root' [$hostname]") do_log ("ERROR: script must be invoked as user 'root' [$hostname]")
and exit (1); and exit (1);
} }
} }
@ -301,7 +303,7 @@ while (@pwgetent = getpwent()) {
push (@accounts, $pwgetent[0]); push (@accounts, $pwgetent[0]);
} }
# remove duplicates (which should not happen (!) but local, LDAP and accounts # remove duplicates (which should not happen (!) but local, LDAP and accounts
# from other sources might trample over each other) # from other sources might trample over each other)
my %uniq_accounts = map { $_, 0 } @accounts; my %uniq_accounts = map { $_, 0 } @accounts;
@accounts = keys %uniq_accounts; @accounts = keys %uniq_accounts;
@ -310,7 +312,7 @@ do_log ("INFO: ".scalar (@accounts)." user accounts found on $hostname");
print Dumper (\@accounts) if $debug; print Dumper (\@accounts) if $debug;
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# read aliases for teams, servers and users # read aliases for teams, servers and users (and resolve group definitions)
# result: %aliases # result: %aliases
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -321,7 +323,7 @@ open (ALIASES, "<", "${run_dir}/alias")
while (<ALIASES>) { while (<ALIASES>) {
my ($key, $value, @values); my ($key, $value, @values);
chomp (); chomp ();
next if (/^$/ || /\#/); next if (/^$/ || /\#/);
s/\s+//g; s/\s+//g;
@ -334,13 +336,45 @@ close (ALIASES);
do_log ("DEBUG: dumping unexpanded aliases:"); do_log ("DEBUG: dumping unexpanded aliases:");
print Dumper (\%aliases) if $debug; print Dumper (\%aliases) if $debug;
# we can nest aliases one level deep, so do a one-level recursive sort of lookup # resolve aliases recursively to a maxium of $max_recursion
# of the remaining '@' aliases. Input should be passed as comma-separated while ($recursion_count <= $max_recursion) {
# string to resolve_aliases so don't forget to smash everything back together # crawl over all items in the hash %aliases
# first. foreach my $key (keys (%aliases)) {
foreach my $key (keys (%aliases)) { # crawl over all items in the array @{aliases{$key}}
my @new_array; my @filtered_array; # these are the working stashes
$aliases{$key} = [resolve_aliases (join (",", @{$aliases{$key}}))]; do_log ("DEBUG: expanded alias $key before recursion $recursion_count [$hostname]");
print Dumper (\@{$aliases{$key}}) if $debug;
foreach my $item (@{$aliases{$key}}) {
# is it a group?
if ($item =~ /^\@/) {
# expand the group if it exists
if ($aliases{$item}) {
# add current and new items to the working stash
if (@new_array) {
push (@new_array, @{$aliases{$item}});
} else {
@new_array = (@{$aliases{$key}}, @{$aliases{$item}});
}
# remove the original group item from the working stash
@filtered_array = grep { $_ ne $item } @new_array;
@new_array = @filtered_array;
} else {
do_log ("WARN: unable to resolve alias $item [$hostname]");
}
# no group, just add the item as-is to working stash
} else {
push (@new_array, $item);
}
}
# filter out dupes
my %seen;
@filtered_array = grep { not $seen{$_}++ } @new_array;
# re-assign working stash back to our original hash key
@{$aliases{$key}} = @filtered_array;
do_log ("DEBUG: expanded alias $key after recursion $recursion_count [$hostname]");
print Dumper (\@{$aliases{$key}}) if $debug;
}
$recursion_count++;
} }
do_log ("INFO: ".scalar (keys (%aliases))." aliases found on $hostname"); do_log ("INFO: ".scalar (keys (%aliases))." aliases found on $hostname");
@ -364,18 +398,18 @@ if (-d "${run_dir}/keys.d" && -f "${run_dir}/keys") {
if (-d "${run_dir}/keys.d") { if (-d "${run_dir}/keys.d") {
do_log ("INFO: local 'keys' are stored in a DIRECTORY on $hostname"); do_log ("INFO: local 'keys' are stored in a DIRECTORY on $hostname");
opendir (KEYS_DIR, "${run_dir}/keys.d") opendir (KEYS_DIR, "${run_dir}/keys.d")
or do_log ("ERROR: cannot open 'keys.d' directory [$! $hostname]") or do_log ("ERROR: cannot open 'keys.d' directory [$! $hostname]")
and exit (1); and exit (1);
while (my $key_file = readdir (KEYS_DIR)) { while (my $key_file = readdir (KEYS_DIR)) {
next if ($key_file =~ /^\./); next if ($key_file =~ /^\./);
push (@key_files, "${run_dir}/keys.d/$key_file"); push (@key_files, "${run_dir}/keys.d/$key_file");
} }
closedir (KEYS_DIR); closedir (KEYS_DIR);
} elsif (-f "${run_dir}/keys") { } elsif (-f "${run_dir}/keys") {
do_log ("INFO: local 'keys' are stored in a FILE on $hostname"); do_log ("INFO: local 'keys' are stored in a FILE on $hostname");
push (@key_files, "${run_dir}/keys"); push (@key_files, "${run_dir}/keys");
} else { } else {
do_log ("ERROR: cannot find any public keys in the repository! [$hostname]") do_log ("ERROR: cannot find any public keys in the repository! [$hostname]")
and exit (1); and exit (1);
} }
@ -390,7 +424,7 @@ foreach my $key_file (@key_files) {
chomp (); chomp ();
next if (/^$/ || /\#/); next if (/^$/ || /\#/);
# check for blacklisting # check for blacklisting
my $key_line = $_; my $key_line = $_;
if (grep (/\Q${key_line}\E/, @blacklist)) { if (grep (/\Q${key_line}\E/, @blacklist)) {
@ -412,8 +446,8 @@ print Dumper(\%keys) if $debug;
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# read access definitions # read access definitions
# result: %access (hash of arrays). The keys are the accounts for which # result: %access (hash of arrays). The keys are the accounts for which
# access control has been defined for this server. The values are an array # access control has been defined for this server. The values are an array
# with all the people who can access the account. # with all the people who can access the account.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -424,7 +458,7 @@ open (ACCESS, "<", "${run_dir}/access")
while (<ACCESS>) { while (<ACCESS>) {
my ($who, $where, $what, @who, @where, @what); my ($who, $where, $what, @who, @where, @what);
chomp (); chomp ();
next if (/^$/ || /\#/); next if (/^$/ || /\#/);
s/\s+//g; s/\s+//g;
@ -437,14 +471,14 @@ while (<ACCESS>) {
do_log ("WARN: ignoring line $. in 'access' due to missing/non-resolving values [$hostname]"); do_log ("WARN: ignoring line $. in 'access' due to missing/non-resolving values [$hostname]");
next; next;
} }
foreach my $account (sort (@what)) { foreach my $account (sort (@what)) {
my @new_array; my @new_array;
foreach my $server (sort (@where)) { foreach my $server (sort (@where)) {
foreach my $person (sort (@who)) { foreach my $person (sort (@who)) {
do_log ("DEBUG: adding access for $account to $person on $server in \%access") do_log ("DEBUG: adding access for $account to $person on $server in \%access")
if ($server eq $hostname); if ($server eq $hostname);
# add person to access list if the entry is for this host # add person to access list if the entry is for this host
push (@new_array, $person) if ($server eq $hostname); push (@new_array, $person) if ($server eq $hostname);
@ -477,7 +511,7 @@ if ($preview && $global) {
while (<ACCESS>) { while (<ACCESS>) {
my ($who, $where, $what, @who, @where, @what); my ($who, $where, $what, @who, @where, @what);
chomp (); chomp ();
next if (/^$/ || /\#/); next if (/^$/ || /\#/);
s/\s+//g; s/\s+//g;
@ -490,14 +524,14 @@ if ($preview && $global) {
do_log ("WARN: ignoring line $. in 'access' due to missing/non-resolving values [$hostname]"); do_log ("WARN: ignoring line $. in 'access' due to missing/non-resolving values [$hostname]");
next; next;
} }
foreach my $account (sort (@what)) { foreach my $account (sort (@what)) {
my @new_array; my @new_array;
foreach my $server (sort (@where)) { foreach my $server (sort (@where)) {
foreach my $person (sort (@who)) { foreach my $person (sort (@who)) {
do_log ("$person|$server|$account") do_log ("$person|$server|$account")
} }
} }
} }
@ -523,7 +557,7 @@ unless ($preview) {
if ($selinux_status eq "Permissive" or $selinux_status eq "Enforcing") { if ($selinux_status eq "Permissive" or $selinux_status eq "Enforcing") {
do_log ("INFO: runtime info: detected active SELinux system on $hostname"); do_log ("INFO: runtime info: detected active SELinux system on $hostname");
$has_selinux = 1; $has_selinux = 1;
} }
# figure out RHEL version (via lsb_release or /etc/redhat-release) # figure out RHEL version (via lsb_release or /etc/redhat-release)
$linux_version = qx#/usr/bin/lsb_release -rs 2>/dev/null | /usr/bin/cut -f1 -d'.'#; $linux_version = qx#/usr/bin/lsb_release -rs 2>/dev/null | /usr/bin/cut -f1 -d'.'#;
chomp ($linux_version); chomp ($linux_version);
@ -539,7 +573,7 @@ unless ($preview) {
$release_string =~ m/release 6/i && do { $release_string =~ m/release 6/i && do {
$linux_version = 6; $linux_version = 6;
last SWITCH_RELEASE; last SWITCH_RELEASE;
}; };
$release_string =~ m/release 7/i && do { $release_string =~ m/release 7/i && do {
$linux_version = 7; $linux_version = 7;
last SWITCH_RELEASE; last SWITCH_RELEASE;
@ -547,7 +581,7 @@ unless ($preview) {
} }
} }
# use fall back in case we cannot determine the version # use fall back in case we cannot determine the version
if (not (defined ($linux_version)) or $linux_version eq "") { if (not (defined ($linux_version)) or $linux_version eq "") {
$selinux_context = 'etc_t'; $selinux_context = 'etc_t';
$linux_version = 'unknown'; $linux_version = 'unknown';
} else { } else {
@ -557,21 +591,21 @@ unless ($preview) {
do_log ("INFO: runtime info: OS major version $linux_version, SELinux context $selinux_context on $hostname"); do_log ("INFO: runtime info: OS major version $linux_version, SELinux context $selinux_context on $hostname");
} else { } else {
do_log ("INFO: runtime info: OS major version $linux_version on $hostname"); do_log ("INFO: runtime info: OS major version $linux_version on $hostname");
} }
last SWITCH_OS; last SWITCH_OS;
}; };
} }
} }
# only add authorized_keys for existing accounts, # only add authorized_keys for existing accounts,
# otherwise revoke access if needed # otherwise revoke access if needed
foreach my $account (sort (@accounts)) { foreach my $account (sort (@accounts)) {
my $access_file = "$access_dir/$account"; my $access_file = "$access_dir/$account";
# only add authorised_keys if there are access definitions # only add authorised_keys if there are access definitions
if ($access{$account}) { if ($access{$account}) {
unless ($preview) { unless ($preview) {
open (KEYFILE, "+>", $access_file) open (KEYFILE, "+>", $access_file)
or do_log ("ERROR: cannot open file for writing in $access_dir [$! $hostname]") or do_log ("ERROR: cannot open file for writing in $access_dir [$! $hostname]")
@ -583,11 +617,11 @@ foreach my $account (sort (@accounts)) {
# only add authorized_keys if $person actually has a key # only add authorized_keys if $person actually has a key
if (exists ($keys{$person})) { if (exists ($keys{$person})) {
# only add authorized_keys if $person actually has an account # only add authorized_keys if $person actually has an account
print KEYFILE "$keys{$person}{keytype} $keys{$person}{key} $real_name\n" print KEYFILE "$keys{$person}{keytype} $keys{$person}{key} $real_name\n"
unless $preview; unless $preview;
do_log ("INFO: granting access to $account for $real_name on $hostname"); do_log ("INFO: granting access to $account for $real_name on $hostname");
} else { } else {
do_log ("INFO: denying access (no key) to $account for $real_name on $hostname"); do_log ("INFO: denying access (no key) to $account for $real_name on $hostname");
} }
} }
close (KEYFILE) unless $preview; close (KEYFILE) unless $preview;
@ -597,12 +631,12 @@ foreach my $account (sort (@accounts)) {
set_file ($access_file, 0644, 0, 0); set_file ($access_file, 0644, 0, 0);
# selinux labels # selinux labels
SWITCH: { SWITCH: {
$os eq "Linux" && do { $os eq "Linux" && do {
if ($has_selinux) { if ($has_selinux) {
system ("/usr/bin/chcon -t $selinux_context $access_file") and system ("/usr/bin/chcon -t $selinux_context $access_file") and
do_log ("WARN: failed to set SELinux context $selinux_context on $access_file [$hostname]"); do_log ("WARN: failed to set SELinux context $selinux_context on $access_file [$hostname]");
}; };
last SWITCH; last SWITCH;
} }
} }
} }
@ -621,14 +655,14 @@ foreach my $account (sort (@accounts)) {
} }
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# alert on/remove extraneous authorized_keys files # alert on/remove extraneous authorized_keys files
# (access files for which no longer a valid UNIX account exists) # (access files for which no longer a valid UNIX account exists)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
do_log ("INFO: checking for extraneous access files ...."); do_log ("INFO: checking for extraneous access files ....");
opendir (ACCESS_DIR, $access_dir) opendir (ACCESS_DIR, $access_dir)
or do_log ("ERROR: cannot open directory $access_dir [$! $hostname]") or do_log ("ERROR: cannot open directory $access_dir [$! $hostname]")
and exit (1); and exit (1);
while (my $access_file = readdir (ACCESS_DIR)) { while (my $access_file = readdir (ACCESS_DIR)) {
next if ($access_file =~ /^\./); next if ($access_file =~ /^\./);
@ -644,7 +678,7 @@ print Dumper (\@zombie_files) if $debug;
# remove if requested and needed # remove if requested and needed
if ($remove && @zombie_files) { if ($remove && @zombie_files) {
my $count = unlink (@zombie_files) my $count = unlink (@zombie_files)
or do_log ("ERROR: cannot remove extraneous access file(s) [$! $hostname]") or do_log ("ERROR: cannot remove extraneous access file(s) [$! $hostname]")
and exit (1); and exit (1);
do_log ("INFO: $count extraneous access files removed $hostname"); do_log ("INFO: $count extraneous access files removed $hostname");
} }
@ -667,20 +701,20 @@ update_ssh.pl - distributes SSH public keys in a desired state model.
=head1 SYNOPSIS =head1 SYNOPSIS
update_ssh.pl[-d|--debug] update_ssh.pl[-d|--debug]
[-h|--help] [-h|--help]
([-p|--preview] [-g|--global]) | [-r|--remove] ([-p|--preview] [-g|--global]) | [-r|--remove]
[-v|--verbose] [-v|--verbose]
[-V|--version] [-V|--version]
=head1 DESCRIPTION =head1 DESCRIPTION
B<update_ssh.pl> distributes SSH keys to the appropriate files (.e. 'authorized_keys') into the C<$access_dir> repository based on the F<access>, F<alias> and F<keys> files. B<update_ssh.pl> distributes SSH keys to the appropriate files (.e. 'authorized_keys') into the C<$access_dir> repository based on the F<access>, F<alias> and F<keys> files.
This script should be run on each host where SSH key authentication is the exclusive method of (remote) authentication. This script should be run on each host where SSH key authentication is the exclusive method of (remote) authentication.
For update SSH public keys must be stored in a generic F<keys> file within the same directory as B<update_ssh.pl> script. For update SSH public keys must be stored in a generic F<keys> file within the same directory as B<update_ssh.pl> script.
Alternatively key files may be stored as set of individual key files within a called sub-directory called F<keys.d>. Alternatively key files may be stored as set of individual key files within a called sub-directory called F<keys.d>.
Both methods are mutually exclusive and the latter always take precedence. Both methods are mutually exclusive and the latter always take precedence.
=head1 CONFIGURATION =head1 CONFIGURATION
@ -693,7 +727,7 @@ B<update_ssh.pl> requires the presence of at least one of the following configur
=item * F<update_ssh.conf.local> =item * F<update_ssh.conf.local>
=back =back
Use F<update_ssh.conf.local> for localized settings per host. Settings in the localized configuration file will always override other values. Use F<update_ssh.conf.local> for localized settings per host. Settings in the localized configuration file will always override other values.
@ -746,12 +780,12 @@ S< >Remove any extraneous 'authorized_keys' files (i.e. belonging to non-e
=item -v | --verbose =item -v | --verbose
S< >Be verbose during exection. S< >Be verbose during exection.
=item -V | --version =item -V | --version
S< >Show version of the script. S< >Show version of the script.
=back =back
=head1 NOTES =head1 NOTES
@ -761,16 +795,8 @@ S< >Show version of the script.
=item * Options may be bundled (e.g. -vp) =item * Options may be bundled (e.g. -vp)
=back =back
=head1 AUTHOR =head1 AUTHOR
(c) KUDOS BVBA, Patrick Van der Veken (c) KUDOS BVBA, Patrick Van der Veken
=head1 history
@(#) 2014-12-04: VRF 1.0.0: first version [Patrick Van der Veken]
@(#) 2014-12-16: VRF 1.0.1: added SELinux context, new config option 'selinux_context' [Patrick Van der Veken]
@(#) 2015-08-08: VRF 1.0.2: small fix for 'cut' command [Patrick Van der Veken]
@(#) 2015-08-15: VRF 1.1.0: replace uname/hostname syscalls, now support for FQDN via $use_fqdn, other fixes [Patrick Van der Veken]
@(#) 2015-08-26: VRF 1.2.0: replace read of /etc/passwd by pwgetent() call, small and not so small fixes [Patrick Van der Veken]