* 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:
parent
4e464ffeb0
commit
af9ed19d6b
@ -13,7 +13,7 @@
|
|||||||
# (leave blank for current user)
|
# (leave blank for current user)
|
||||||
SUDO_TRANSFER_USER=""
|
SUDO_TRANSFER_USER=""
|
||||||
|
|
||||||
# name of the OS group that should own the SUDO controls files
|
# name of the UNIX group that should own the SUDO controls files (must exist already)
|
||||||
SUDO_OWNER_GROUP="sudoadmin"
|
SUDO_OWNER_GROUP="sudoadmin"
|
||||||
|
|
||||||
# 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]
|
||||||
@ -45,7 +45,7 @@ VISUDO_BIN="/usr/sbin/visudo"
|
|||||||
# 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"
|
||||||
|
|
||||||
|
762
manage_sudo.sh
762
manage_sudo.sh
File diff suppressed because it is too large
Load Diff
213
update_sudo.pl
213
update_sudo.pl
@ -43,21 +43,23 @@ use File::Temp qw(tempfile);
|
|||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
|
|
||||||
# ------------------------- CONFIGURATION starts here -------------------------
|
# ------------------------- CONFIGURATION starts here -------------------------
|
||||||
# define the V.R.F (version/release/fix)
|
# define the version (YYYY-MM-DD)
|
||||||
my $MY_VRF = "1.1.4";
|
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_sudo.conf";
|
my $global_config_file = "update_sudo.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_sudo.conf.local";
|
my $local_config_file = "update_sudo.conf.local";
|
||||||
|
# maxiumum level of recursion for alias resolution
|
||||||
|
my $max_recursion = 5;
|
||||||
# selinux context label of sudoers fragment files
|
# selinux context label of sudoers fragment files
|
||||||
my $selinux_context = "etc_t";
|
my $selinux_context = "etc_t";
|
||||||
# ------------------------- CONFIGURATION ends here ---------------------------
|
# ------------------------- CONFIGURATION ends here ---------------------------
|
||||||
# initialize variables
|
# initialize variables
|
||||||
my ($debug, $verbose, $preview, $global, $use_fqdn) = (0,0,0,0,0);
|
my ($debug, $verbose, $preview, $global, $use_fqdn) = (0,0,0,0,0);
|
||||||
my (@config_files, $fragments_dir, $visudo_bin, $immutable_self_file, $immutable_self_cmd);
|
my (@config_files, $fragments_dir, $visudo_bin, $immutable_self_file, $immutable_self_cmd);
|
||||||
my (%options, @uname, %aliases, %frags, @grants);
|
my (%options, @uname, %aliases, %frags, @grants);
|
||||||
my ($os, $host, $hostname, $run_dir);
|
my ($os, $host, $hostname, $run_dir);
|
||||||
my ($selinux_status, $has_selinux) = ("",0);
|
my ($selinux_status, $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:/) {
|
||||||
@ -87,7 +89,7 @@ sub parse_config_file {
|
|||||||
my $config_file = shift;
|
my $config_file = shift;
|
||||||
|
|
||||||
unless (open (CONF_FD, "<", $config_file)) {
|
unless (open (CONF_FD, "<", $config_file)) {
|
||||||
do_log ("ERROR: failed to open the configuration file ${config_file} [$! $hostname]")
|
do_log ("ERROR: failed to open the configuration file ${config_file} [$! $hostname]")
|
||||||
and exit (1);
|
and exit (1);
|
||||||
}
|
}
|
||||||
while (<CONF_FD>) {
|
while (<CONF_FD>) {
|
||||||
@ -118,7 +120,7 @@ sub parse_config_file {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# parameter checks
|
# parameter checks
|
||||||
if (not defined ($immutable_self_file) or $immutable_self_file eq "") {
|
if (not defined ($immutable_self_file) or $immutable_self_file eq "") {
|
||||||
do_log ("ERROR: 'immutable_self_file' parameter not defined [$hostname]")
|
do_log ("ERROR: 'immutable_self_file' parameter not defined [$hostname]")
|
||||||
@ -138,7 +140,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)
|
||||||
@ -153,14 +155,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,12 +187,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
|
||||||
@ -207,7 +209,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");
|
||||||
}
|
}
|
||||||
@ -235,7 +237,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +252,7 @@ unless ($preview and $global) {
|
|||||||
if (-d $fragments_dir) {
|
if (-d $fragments_dir) {
|
||||||
do_log ("INFO: host is under SUDO control via $fragments_dir");
|
do_log ("INFO: host is under SUDO control via $fragments_dir");
|
||||||
} else {
|
} else {
|
||||||
do_log ("ERROR: host is not under SUDO control [$hostname]")
|
do_log ("ERROR: host is not under SUDO control [$hostname]")
|
||||||
and exit (1);
|
and exit (1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,7 +260,7 @@ unless ($preview and $global) {
|
|||||||
# is syntax checking possible? (not for global preview)
|
# is syntax checking possible? (not for global preview)
|
||||||
unless ($preview and $global) {
|
unless ($preview and $global) {
|
||||||
unless (-x $visudo_bin) {
|
unless (-x $visudo_bin) {
|
||||||
do_log ("ERROR: 'visudo' tool could not be found, will not continue [$hostname]")
|
do_log ("ERROR: 'visudo' tool could not be found, will not continue [$hostname]")
|
||||||
and exit (1);
|
and exit (1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,7 +271,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,7 +296,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;
|
||||||
@ -307,13 +309,44 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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");
|
||||||
@ -321,8 +354,8 @@ do_log ("DEBUG: dumping expanded aliases:");
|
|||||||
print Dumper (\%aliases) if $debug;
|
print Dumper (\%aliases) if $debug;
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# read SUDO fragments stored in a single 'fragments' file or in
|
# read SUDO fragments stored in a single 'fragments' file or in
|
||||||
# individual fragment files from a 'fragments.d' directory
|
# individual fragment files from a 'fragments.d' directory
|
||||||
# result: %frags
|
# result: %frags
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -337,43 +370,43 @@ if (-d "${run_dir}/fragments.d" && -f "${run_dir}/fragments") {
|
|||||||
if (-d "${run_dir}/fragments.d") {
|
if (-d "${run_dir}/fragments.d") {
|
||||||
do_log ("INFO: local 'fragments' are stored in a DIRECTORY on $hostname");
|
do_log ("INFO: local 'fragments' are stored in a DIRECTORY on $hostname");
|
||||||
opendir (FRAGS_DIR, "${run_dir}/fragments.d")
|
opendir (FRAGS_DIR, "${run_dir}/fragments.d")
|
||||||
or do_log ("ERROR: cannot open 'fragments.d' directory [$! $hostname]")
|
or do_log ("ERROR: cannot open 'fragments.d' directory [$! $hostname]")
|
||||||
and exit (1);
|
and exit (1);
|
||||||
while (my $frag_file = readdir (FRAGS_DIR)) {
|
while (my $frag_file = readdir (FRAGS_DIR)) {
|
||||||
next if ($frag_file =~ /^\./);
|
next if ($frag_file =~ /^\./);
|
||||||
push (@frag_files, "${run_dir}/fragments.d/$frag_file");
|
push (@frag_files, "${run_dir}/fragments.d/$frag_file");
|
||||||
}
|
}
|
||||||
closedir (FRAGS_DIR);
|
closedir (FRAGS_DIR);
|
||||||
} elsif (-f "${run_dir}/fragments") {
|
} elsif (-f "${run_dir}/fragments") {
|
||||||
do_log ("INFO: local 'fragments' are stored in a FILE on $hostname");
|
do_log ("INFO: local 'fragments' are stored in a FILE on $hostname");
|
||||||
push (@frag_files, "${run_dir}/fragments");
|
push (@frag_files, "${run_dir}/fragments");
|
||||||
} else {
|
} else {
|
||||||
do_log ("ERROR: cannot find any SUDO fragments in the repository! [$hostname]")
|
do_log ("ERROR: cannot find any SUDO fragments in the repository! [$hostname]")
|
||||||
and exit (1);
|
and exit (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
# process 'fragments' files
|
# process 'fragments' files
|
||||||
foreach my $frag_file (@frag_files) {
|
foreach my $frag_file (@frag_files) {
|
||||||
open (FRAGS, "<", $frag_file)
|
open (FRAGS, "<", $frag_file)
|
||||||
or do_log ("ERROR: cannot read 'fragments' file [$! $hostname]")
|
or do_log ("ERROR: cannot read 'fragments' file [$! $hostname]")
|
||||||
and exit (1);
|
and exit (1);
|
||||||
do_log ("INFO: reading SUDO fragments from file: $frag_file");
|
do_log ("INFO: reading SUDO fragments from file: $frag_file");
|
||||||
|
|
||||||
my @frag_file = <FRAGS>;
|
my @frag_file = <FRAGS>;
|
||||||
|
|
||||||
# check for fragments header(s): if there is no fragment header, then we
|
# check for fragments header(s): if there is no fragment header, then we
|
||||||
# consider this a single fragment file, otherwise we consider it a
|
# consider this a single fragment file, otherwise we consider it a
|
||||||
# collection of fragments that needs to be broken down in individual fragments
|
# collection of fragments that needs to be broken down in individual fragments
|
||||||
|
|
||||||
if (grep { /^%%%/s } @frag_file) {
|
if (grep { /^%%%/s } @frag_file) {
|
||||||
|
|
||||||
do_log ("INFO: fragment file $frag_file contains multiple fragments, parsing ...");
|
do_log ("INFO: fragment file $frag_file contains multiple fragments, parsing ...");
|
||||||
|
|
||||||
my ($frag_file, $frag_def);
|
my ($frag_file, $frag_def);
|
||||||
my $count = 1;
|
my $count = 1;
|
||||||
|
|
||||||
foreach (@frag_file) {
|
foreach (@frag_file) {
|
||||||
|
|
||||||
# first header found
|
# first header found
|
||||||
if (/^%%%/ && (not defined ($frag_def) or $frag_def eq "")) {
|
if (/^%%%/ && (not defined ($frag_def) or $frag_def eq "")) {
|
||||||
|
|
||||||
@ -397,7 +430,7 @@ foreach my $frag_file (@frag_files) {
|
|||||||
chomp ($frag_file);
|
chomp ($frag_file);
|
||||||
unless (defined ($frag_file) && $frag_file ne "") {
|
unless (defined ($frag_file) && $frag_file ne "") {
|
||||||
do_log ("WARN: no fragment file name found in header at line $count [$hostname]")
|
do_log ("WARN: no fragment file name found in header at line $count [$hostname]")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
# process fragment definition
|
# process fragment definition
|
||||||
$frag_def .= $_;
|
$frag_def .= $_;
|
||||||
@ -413,7 +446,7 @@ foreach my $frag_file (@frag_files) {
|
|||||||
$frag_file = fileparse ($frag_file, qr/\.[^.]*/);
|
$frag_file = fileparse ($frag_file, qr/\.[^.]*/);
|
||||||
do_log ("INFO: fragment file $frag_file contains only 1 fragment on $hostname");
|
do_log ("INFO: fragment file $frag_file contains only 1 fragment on $hostname");
|
||||||
$frags{$frag_file} = join (/\n/, @frag_file);
|
$frags{$frag_file} = join (/\n/, @frag_file);
|
||||||
}
|
}
|
||||||
close (FRAGS);
|
close (FRAGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,13 +469,13 @@ if ($? == 0) {
|
|||||||
do_log ("INFO: syntax check of sudo fragments is OK on $hostname");
|
do_log ("INFO: syntax check of sudo fragments is OK on $hostname");
|
||||||
unlink $sudo_file;
|
unlink $sudo_file;
|
||||||
} else {
|
} else {
|
||||||
do_log "ERROR: visudo check failed: ".join ("\n", @syntax_check)." [$hostname]"
|
do_log "ERROR: visudo check failed: ".join ("\n", @syntax_check)." [$hostname]"
|
||||||
and exit(1);
|
and exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# read grant definitions
|
# read grant definitions
|
||||||
# result: @grants (array): fragments for which grants have been defined
|
# result: @grants (array): fragments for which grants have been defined
|
||||||
# for this server.
|
# for this server.
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -453,7 +486,7 @@ open (GRANTS, "<", "${run_dir}/grants")
|
|||||||
while (<GRANTS>) {
|
while (<GRANTS>) {
|
||||||
|
|
||||||
my ($what, $where, @what, @where);
|
my ($what, $where, @what, @where);
|
||||||
|
|
||||||
chomp ();
|
chomp ();
|
||||||
next if (/^$/ || /\#/);
|
next if (/^$/ || /\#/);
|
||||||
s/\s+//g;
|
s/\s+//g;
|
||||||
@ -465,10 +498,10 @@ while (<GRANTS>) {
|
|||||||
do_log ("WARN: ignoring line $. in 'grants' due to missing/non-resolving values [$hostname]");
|
do_log ("WARN: ignoring line $. in 'grants' due to missing/non-resolving values [$hostname]");
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $grant (sort (@what)) {
|
foreach my $grant (sort (@what)) {
|
||||||
foreach my $server (sort (@where)) {
|
foreach my $server (sort (@where)) {
|
||||||
do_log ("DEBUG: adding grants for $grant on $server in \@grants")
|
do_log ("DEBUG: adding grants for $grant on $server in \@grants")
|
||||||
if ($server eq $hostname);
|
if ($server eq $hostname);
|
||||||
# add sudo fragment to grants list if the entry is for this host
|
# add sudo fragment to grants list if the entry is for this host
|
||||||
push (@grants, $grant) if ($server eq $hostname);
|
push (@grants, $grant) if ($server eq $hostname);
|
||||||
@ -494,7 +527,7 @@ if ($preview && $global) {
|
|||||||
while (<GRANTS>) {
|
while (<GRANTS>) {
|
||||||
|
|
||||||
my ($what, $where, @what, @where);
|
my ($what, $where, @what, @where);
|
||||||
|
|
||||||
chomp ();
|
chomp ();
|
||||||
next if (/^$/ || /\#/);
|
next if (/^$/ || /\#/);
|
||||||
s/\s+//g;
|
s/\s+//g;
|
||||||
@ -506,15 +539,15 @@ if ($preview && $global) {
|
|||||||
do_log ("WARN: ignoring line $. in 'grants' due to missing/non-resolving values [$hostname]");
|
do_log ("WARN: ignoring line $. in 'grants' due to missing/non-resolving values [$hostname]");
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $grant (sort (@what)) {
|
foreach my $grant (sort (@what)) {
|
||||||
foreach my $server (sort (@where)) {
|
foreach my $server (sort (@where)) {
|
||||||
do_log ("$grant|$server")
|
do_log ("$grant|$server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
close (GRANTS);
|
close (GRANTS);
|
||||||
|
|
||||||
exit (0);
|
exit (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,48 +560,48 @@ do_log ("INFO: (de)-activating SUDO fragments ....");
|
|||||||
# check for SELinux
|
# check for SELinux
|
||||||
unless ($preview) {
|
unless ($preview) {
|
||||||
SWITCH: {
|
SWITCH: {
|
||||||
$os eq "Linux" && do {
|
$os eq "Linux" && do {
|
||||||
$selinux_status = qx#/usr/sbin/getenforce 2>/dev/null#;
|
$selinux_status = qx#/usr/sbin/getenforce 2>/dev/null#;
|
||||||
chomp ($selinux_status);
|
chomp ($selinux_status);
|
||||||
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;
|
||||||
}
|
}
|
||||||
last SWITCH;
|
last SWITCH;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# remove previous fragment files first
|
# remove previous fragment files first
|
||||||
opendir (FRAGS_DIR, "${fragments_dir}")
|
opendir (FRAGS_DIR, "${fragments_dir}")
|
||||||
or do_log ("ERROR: cannot open ${fragments_dir} directory [$! $hostname]")
|
or do_log ("ERROR: cannot open ${fragments_dir} directory [$! $hostname]")
|
||||||
and exit (1);
|
and exit (1);
|
||||||
while (my $frag_file = readdir (FRAGS_DIR)) {
|
while (my $frag_file = readdir (FRAGS_DIR)) {
|
||||||
next if ($frag_file =~ /^\./ or $frag_file eq $immutable_self_file);
|
next if ($frag_file =~ /^\./ or $frag_file eq $immutable_self_file);
|
||||||
# safe to ignore . (dot) files as sudo also does as well
|
# safe to ignore . (dot) files as sudo also does as well
|
||||||
|
|
||||||
unless ($preview) {
|
unless ($preview) {
|
||||||
|
|
||||||
my $frag_file = "$fragments_dir/$frag_file";
|
my $frag_file = "$fragments_dir/$frag_file";
|
||||||
|
|
||||||
if (unlink ($frag_file)) {
|
if (unlink ($frag_file)) {
|
||||||
do_log ("INFO: de-activating fragment file $frag_file on $hostname");
|
do_log ("INFO: de-activating fragment file $frag_file on $hostname");
|
||||||
} else {
|
} else {
|
||||||
do_log ("ERROR: cannot de-activate fragment file(s) [$! $hostname]");
|
do_log ("ERROR: cannot de-activate fragment file(s) [$! $hostname]");
|
||||||
exit (1);
|
exit (1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
closedir (FRAGS_DIR);
|
closedir (FRAGS_DIR);
|
||||||
|
|
||||||
# re-active current fragments
|
# re-active current fragments
|
||||||
foreach my $grant (@grants) {
|
foreach my $grant (@grants) {
|
||||||
|
|
||||||
# do not create empty sudo files
|
# do not create empty sudo files
|
||||||
if (exists ($frags{$grant})) {
|
if (exists ($frags{$grant})) {
|
||||||
|
|
||||||
my $sudo_file = "$fragments_dir/$grant";
|
my $sudo_file = "$fragments_dir/$grant";
|
||||||
|
|
||||||
unless ($preview) {
|
unless ($preview) {
|
||||||
open (SUDO_FILE, "+>", $sudo_file)
|
open (SUDO_FILE, "+>", $sudo_file)
|
||||||
or do_log ("ERROR: cannot open file for writing in $fragments_dir [$! $hostname]")
|
or do_log ("ERROR: cannot open file for writing in $fragments_dir [$! $hostname]")
|
||||||
@ -577,21 +610,21 @@ foreach my $grant (@grants) {
|
|||||||
print SUDO_FILE "$frags{$grant}\n" unless $preview;
|
print SUDO_FILE "$frags{$grant}\n" unless $preview;
|
||||||
do_log ("INFO: activating fragment $grant on $hostname");
|
do_log ("INFO: activating fragment $grant on $hostname");
|
||||||
close (SUDO_FILE) unless $preview;
|
close (SUDO_FILE) unless $preview;
|
||||||
|
|
||||||
# set permissions to world readable & SELinux contexts
|
# set permissions to world readable & SELinux contexts
|
||||||
unless ($preview) {
|
unless ($preview) {
|
||||||
SWITCH: {
|
SWITCH: {
|
||||||
$os eq "HP-UX" && do {
|
$os eq "HP-UX" && do {
|
||||||
set_file ($sudo_file, 0440, 2, 2);
|
set_file ($sudo_file, 0440, 2, 2);
|
||||||
last SWITCH;
|
last SWITCH;
|
||||||
};
|
};
|
||||||
$os eq "Linux" && do {
|
$os eq "Linux" && do {
|
||||||
if ($has_selinux) {
|
if ($has_selinux) {
|
||||||
system ("/usr/bin/chcon -t $selinux_context $sudo_file") == 0 or
|
system ("/usr/bin/chcon -t $selinux_context $sudo_file") == 0 or
|
||||||
do_log ("WARN: failed to set SELinux context $selinux_context on $sudo_file [$hostname]");
|
do_log ("WARN: failed to set SELinux context $selinux_context on $sudo_file [$hostname]");
|
||||||
}
|
}
|
||||||
set_file ($sudo_file, 0440, 0, 0);
|
set_file ($sudo_file, 0440, 0, 0);
|
||||||
last SWITCH;
|
last SWITCH;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -613,20 +646,20 @@ unless ($preview) {
|
|||||||
print SELF_FILE $immutable_self_cmd."\n";
|
print SELF_FILE $immutable_self_cmd."\n";
|
||||||
do_log ("INFO: activating immutable self fragment $immutable_self_file on $hostname");
|
do_log ("INFO: activating immutable self fragment $immutable_self_file on $hostname");
|
||||||
SWITCH: {
|
SWITCH: {
|
||||||
$os eq "HP-UX" && do {
|
$os eq "HP-UX" && do {
|
||||||
set_file ($self_file, 0440, 2, 2);
|
set_file ($self_file, 0440, 2, 2);
|
||||||
last SWITCH;
|
last SWITCH;
|
||||||
};
|
};
|
||||||
$os eq "Linux" && do {
|
$os eq "Linux" && do {
|
||||||
if ($has_selinux) {
|
if ($has_selinux) {
|
||||||
system ("/usr/bin/chcon -t $selinux_context $self_file") == 0 or
|
system ("/usr/bin/chcon -t $selinux_context $self_file") == 0 or
|
||||||
do_log ("WARN: failed to set SELinux context $selinux_context on $self_file [$hostname]");
|
do_log ("WARN: failed to set SELinux context $selinux_context on $self_file [$hostname]");
|
||||||
}
|
}
|
||||||
set_file ($self_file, 0440, 0, 0);
|
set_file ($self_file, 0440, 0, 0);
|
||||||
last SWITCH;
|
last SWITCH;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
close (SELF_FILE);
|
close (SELF_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit (0);
|
exit (0);
|
||||||
@ -647,20 +680,20 @@ update_sudo.pl - distributes SUDO fragments according to a desired state model.
|
|||||||
|
|
||||||
=head1 SYNOPSIS
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
update_sudo.pl [-d|--debug]
|
update_sudo.pl [-d|--debug]
|
||||||
[-h|--help]
|
[-h|--help]
|
||||||
([-p|--preview] [-g|--global])
|
([-p|--preview] [-g|--global])
|
||||||
[-v|--verbose]
|
[-v|--verbose]
|
||||||
[-V|--version]
|
[-V|--version]
|
||||||
|
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
B<update_sudo.pl> distributes SUDO fragments into the C<$fragments_dir> repository based on the F<grants>, F<alias> and F<fragments> files.
|
B<update_sudo.pl> distributes SUDO fragments into the C<$fragments_dir> repository based on the F<grants>, F<alias> and F<fragments> files.
|
||||||
This script should be run on each host where SUDO is the required method of privilege escalation.
|
This script should be run on each host where SUDO is the required method of privilege escalation.
|
||||||
|
|
||||||
For update SUDO fragments must be stored in a generic F<fragments> file within the same directory as B<update_sudo.pl> script.
|
For update SUDO fragments must be stored in a generic F<fragments> file within the same directory as B<update_sudo.pl> script.
|
||||||
Alternatively SUDO fragments may be stored as set of individual files within a called sub-directory called F<fragments.d>.
|
Alternatively SUDO fragments may be stored as set of individual files within a called sub-directory called F<fragments.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
|
||||||
@ -673,7 +706,7 @@ B<update_sudo.pl> requires the presence of at least one of the following configu
|
|||||||
|
|
||||||
=item * F<update_sudo.conf.local>
|
=item * F<update_sudo.conf.local>
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
Use F<update_sudo.conf.local> for localized settings per host. Settings in the localized configuration file will always override other values.
|
Use F<update_sudo.conf.local> for localized settings per host. Settings in the localized configuration file will always override other values.
|
||||||
|
|
||||||
@ -714,12 +747,12 @@ S< >Must be used in conjunction with the --preview option. This will dump
|
|||||||
=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
|
||||||
|
|
||||||
@ -729,20 +762,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 [Patrick Van der Veken]
|
|
||||||
@(#) 2014-12-16: VRF 1.0.2: fixed a problem with the immutable self fragment code [Patrick Van der Veken]
|
|
||||||
@(#) 2015-02-02: VRF 1.0.3: changed 'basename' into 'fileparse' call to support fragment files with extensions [Patrick Van der Veken]
|
|
||||||
@(#) 2015-08-18: 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.1.1: small and not so small fixes [Patrick Van der Veken]
|
|
||||||
@(#) 2015-08-27: VRF 1.1.2: small fix [Patrick Van der Veken]
|
|
||||||
@(#) 2015-09-09: VRF 1.1.3: small selinux fix [Patrick Van der Veken]
|
|
||||||
@(#) 2015-09-09: VRF 1.1.4: wrong handling of RC=0 in system() [Patrick Van der Veken]
|
|
Loading…
x
Reference in New Issue
Block a user