Compare commits

..

27 Commits

Author SHA1 Message Date
Patrick Van der Veken
29322b6a48 Fix quoting errors + messages 2025-07-28 19:14:41 +02:00
78d2d6aff1 * Add flag ignore_errors to allow uninterrupted fragment deployment
* Other minor updates
2025-04-27 09:08:14 +02:00
813315bc4f Update link 2024-08-03 20:21:15 +02:00
6e1af4dd02 Merge branch 'master' of github.com:patvdv/sudo_controls 2021-06-16 22:31:30 +02:00
518e049a12 Fixes in resolve_alias() & resolve_targets() 2021-06-16 22:29:35 +02:00
Patrick Van der Veken
0ceb4120be Bad copy/paste, urgh 2021-03-24 11:27:30 +01:00
e601284bc8 Update documentation link 2021-02-01 21:05:40 +01:00
Patrick Van der Veken
1c0d1139aa ksh88 + whitespace fix 2021-01-11 16:05:54 +01:00
Patrick Van der Veken
a7ee16d454 Fixed problem with old AWK's max input line of 3000 bytes 2021-01-09 14:22:24 +01:00
Patrick Van der Veken
330e413b98 * added support for SELinux (CentOS/RHEL 8.x)
* various fixes
2020-12-30 19:23:11 +01:00
Patrick Van der Veken
af291c07e0 Fixed bug in sftp_file(): when file does not contain a directory path, the $TRANSFER_DIR was wrongly calculated 2020-05-28 17:37:34 +02:00
patvdv
0adabe65cc Fix for ksh93 -> ksh88 compatability (Peter Stoops) 2018-11-24 10:15:03 +01:00
patvdv
e5e2981d88 Doc update 2018-11-03 23:36:40 +01:00
patvdv
465c2cec90 Doc update 2018-11-03 23:21:39 +01:00
patvdv
7aee2b336d Doc update 2018-11-03 16:59:35 +01:00
patvdv
af9ed19d6b * 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)
2018-11-03 16:35:59 +01:00
patvdv
4e464ffeb0 Fix 2018-10-29 19:31:38 +01:00
patvdv
0ef4fa6dd9 Fix linter errors 2018-10-28 21:55:20 +01:00
Van der Veken Patrick
73f0f146d1 Removed HISTORY comments 2017-10-26 09:11:44 +02:00
patvdv
dc86b054aa fix for DO_SLAVE, improved check_root_user() calls (VRF 1.5.2) [Patrick Van der Veken] 2015-12-13 15:07:13 +01:00
patvdv
09a1b75b3c Update doc 2015-10-10 21:17:20 +02:00
patvdv
554661e502 simplified handling of SSH agent handling, obsoleted DO_SLAVE_SSH_AGENT option (VRF 1.5.1) [Patrick Van der Veken] 2015-10-10 21:15:16 +02:00
patvdv
50c62d7a4b Update doc 2015-10-03 20:08:37 +02:00
patvdv
787966ff39 Update doc for master->slave operations 2015-10-03 20:06:15 +02:00
patvdv
e38facb5d9 added --slave option, 3 new configuration parameters & supporting functions for master->slave operations, several bug fixes including sudoers.d ownerships on HP-UX (VRF 1.5.0) [Patrick Van der Veken] 2015-10-03 20:04:23 +02:00
patvdv
2a878a12bb Update doc 2015-09-27 17:59:16 +02:00
patvdv
33f2c00db0 added SSH host keys discovery, re-assigned '-d' command-line option to this function, 2 new parameters in manage_sudo.conf (VRF 1.4.0) [Patrick Van der Veken] 2015-09-27 17:53:53 +02:00
6 changed files with 1307 additions and 403 deletions

View File

@ -1,18 +1,34 @@
# SUDO Controls <p align="center"><img src="logo.png" alt="SUDO Controls Logo"></p>
## What's new
:loudspeaker: **27/04/2025**:
* added the `ignore_errors` flag to allow uninterrupted deployment of fragements.
## About
SUDO Controls is a light-weight **SUDO fragments/rules** distribution & management framework which: SUDO Controls is a light-weight **SUDO fragments/rules** distribution & management framework which:
* uses a **desired state** model: SUDO Controls pushes fragments from a master server onto client host and applies them according to the central configuration. * uses a **desired state** model: SUDO Controls *pushes* fragments from a master (or slave) server onto client host(s) and applies them according to the central configuration.
* uses **SSH** as **transport** mechanism: SUDO Controls connects to client hosts through the secure path of SSH. * uses **SSH** as **transport** mechanism: SUDO Controls connects to client hosts through the secure path of SSH.
* supports a **Master→Slave→Client** model so that information can be propagated within more complex LAN set-ups.
* performs operations with **least privileges**: copy/distribute operations are performed with a low-privileged account. Only the actual snippet updates requires super-user privileges. * performs operations with **least privileges**: copy/distribute operations are performed with a low-privileged account. Only the actual snippet updates requires super-user privileges.
* uses a **two-stage** approach to activate **SUDO fragments**: copy (or distribute) and apply. Fragments are first copied into a temporary location on each client hosts - the holding directory - and not applied automatically. Applying or activating fragments on a client host is a separate operation which can be triggered either locally or remotely (from the SUDO master) * uses a **two-stage** approach to activate **SUDO fragments**: copy (or distribute) and apply. Fragments are first copied into a temporary location on each client hosts - the holding directory - and not applied automatically. Applying or activating fragments on a client host is a separate operation which can be triggered either locally or remotely (from the SUDO master)
* allows the use of (nested) **groups** in the master configuration: users, fragments and hosts can be grouped in the SUDO master configuration files to allow a simplified configuration. Nesting of groups is allowed up to one level deep. * allows the use of (nested) **groups** in the master configuration: fragments and hosts can be grouped in the SUDO master configuration files to allow a simplified configuration. Nesting of groups is allowed up to *5 levels* deep.
* allows the use of (nested) **groups** in the specification of the *push* targets. Either via the `--targets` command-line parameter or via the `targets` configuration file.
* can discover SSH host public keys to (re)create `known_hosts` file(s) for a large amount of hosts
* requires **no client agent** component and is **stateless**: SUDO Controls performs operations by pushing fragments or commands to client hosts. Update processes on the client hosts will only be started on-demand. If the SUDO master is - for whatever reason - unavailable then active fragments on a client host remain in place. * requires **no client agent** component and is **stateless**: SUDO Controls performs operations by pushing fragments or commands to client hosts. Update processes on the client hosts will only be started on-demand. If the SUDO master is - for whatever reason - unavailable then active fragments on a client host remain in place.
* is **easy** to **configure** and **maintain** (command-line based): the configuration is stored in a limited number of flat files and be easily updated. A very rudimentary syntax checking facility is also available to check the consistency of the most important (master) configuration files. * is **easy** to **configure** and **maintain** (command-line based): the configuration is stored in a limited number of flat files and be easily updated. A very rudimentary syntax checking facility is also available to check the consistency of the most important (master) configuration files.
More documentation can be found at http://www.kudos.be/Projects/SUDO_Controls.html More documentation can be found at <https://www.kudos.be/sudo_controls/>
*Logo created with [Free Logo Maker](https://logomakr.com)*

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -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]
@ -32,7 +32,8 @@ LOCAL_DIR="/etc/sudo_master"
REMOTE_DIR="/etc/sudo_controls/holding" REMOTE_DIR="/etc/sudo_controls/holding"
# name of the user account performing the SUDO controls update # name of the user account performing the SUDO controls update
# (leave blank for current user but user should have remote sudo root privs) # (leave blank for current user running script)
# user should have remote sudo root privs (except when using user 'root')
SUDO_UPDATE_USER="" SUDO_UPDATE_USER=""
# options to pass to update_sudo.pl when executing a key update # options to pass to update_sudo.pl when executing a key update
@ -41,6 +42,20 @@ SUDO_UPDATE_OPTS="--verbose"
# path to the visudo tool # path to the visudo tool
VISUDO_BIN="/usr/sbin/visudo" VISUDO_BIN="/usr/sbin/visudo"
# path to the ssh-keyscan too
SSH_KEYSCAN_BIN="/usr/bin/ssh-keyscan"
# 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
SSH_KEYSCAN_ARGS="-t rsa"
# whether to start an SSH agent process for the master->client operations [0=No; 1=Yes]
DO_SSH_AGENT=0
# location of the SSH private key that should be added to the SSH agent process
# must be a passphrase-less key (required when using DO_SSH_AGENT)
SSH_PRIVATE_KEY="$HOME/.ssh/id_rsa"
# maximum number of background process to spawn (~maxuprc, ~nstrpty etc) # maximum number of background process to spawn (~maxuprc, ~nstrpty etc)
MAX_BACKGROUND_PROCS=30 MAX_BACKGROUND_PROCS=30

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,9 @@
# use short hostnames or FQDN (0=short names; 1=FQDN) [default: 0] # use short hostnames or FQDN (0=short names; 1=FQDN) [default: 0]
use_fqdn=1 use_fqdn=1
# ignore errors during fragment deployment (0=no; 1=yes [default: 0])
ignore_errors=0
# target directory for sudo fragment files # target directory for sudo fragment files
fragments_dir=/etc/sudo_controls/sudoers.d fragments_dir=/etc/sudo_controls/sudoers.d

View File

@ -2,7 +2,7 @@
#****************************************************************************** #******************************************************************************
# @(#) update_sudo.pl # @(#) update_sudo.pl
#****************************************************************************** #******************************************************************************
# @(#) Copyright (C) 2014 by KUDOS BVBA <info@kudos.be>. All rights reserved. # @(#) Copyright (C) 2014 by KUDOS BV <info@kudos.be>. All rights reserved.
# #
# This program is a free software; you can redistribute it and/or modify # This program is a free software; you can redistribute it and/or modify
# it under the same terms of the GNU General Public License as published by # it under the same terms of the GNU General Public License as published by
@ -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 = "2025-04-27";
# 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, $ignore_errors) = (0,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);
$|++; $|++;
@ -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>) {
@ -96,10 +98,14 @@ sub parse_config_file {
if (/^\s*$/ || /^#/) { if (/^\s*$/ || /^#/) {
next; next;
} else { } else {
if (/^\s*use_fqdn\s*=\s*([0-9]+)\s*$/) { if (/^\s*use_fqdn\s*=\s*(0|1)\s*$/) {
$use_fqdn = $1; $use_fqdn = $1;
do_log ("DEBUG: picking up setting: use_fqdn=${use_fqdn}"); do_log ("DEBUG: picking up setting: use_fqdn=${use_fqdn}");
} }
if (/^\s*ignore_errors\s*=\s*(0|1)\s*$/) {
$ignore_errors = $1;
do_log ("DEBUG: picking up setting: ignore_errors=${ignore_errors}");
}
if (/^\s*fragments_dir\s*=\s*([0-9A-Za-z_\-\.\/~]+)\s*$/) { if (/^\s*fragments_dir\s*=\s*([0-9A-Za-z_\-\.\/~]+)\s*$/) {
$fragments_dir = $1; $fragments_dir = $1;
do_log ("DEBUG: picking up setting: fragments_dir=${fragments_dir}"); do_log ("DEBUG: picking up setting: fragments_dir=${fragments_dir}");
@ -154,12 +160,24 @@ sub set_file {
my ($file, $perm, $uid, $gid) = @_; my ($file, $perm, $uid, $gid) = @_;
chmod ($perm, "$file") my $rc = chmod ($perm, "$file");
or do_log ("ERROR: cannot set permissions on $file [$! $hostname]") if (!$rc) {
and exit (1); if ($ignore_errors) {
chown ($uid, $gid, "$file") do_log ("ERROR: cannot set permissions on $file [$!/$hostname] -- IGNORED");
or do_log ("ERROR: cannot set ownerships on $file [$! $hostname]") } else {
and exit (1); do_log ("ERROR: cannot set permissions on $file [$!/$hostname]");
exit (1);
}
}
my $rc = chown ($uid, $gid, "$file");
if (!$rc) {
if ($ignore_errors) {
do_log ("ERROR: cannot set ownerships on $file [$!/$hostname] -- IGNORED");
} else {
do_log ("ERROR: cannot set ownerships on $file [$!/$hostname]");
exit (1);
}
}
return (1); return (1);
} }
@ -180,6 +198,7 @@ if ( @ARGV > 0 ) {
debug|d debug|d
help|h|? help|h|?
global|g global|g
ignore|i
preview|p preview|p
verbose|v verbose|v
version|V version|V
@ -190,7 +209,7 @@ 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,6 +221,10 @@ if ($options{'help'}) {
if ($options{'global'}) { if ($options{'global'}) {
$global = 1; $global = 1;
} }
# check ignore parameter
if ($options{'ignore'}) {
$ignore_errors = 1;
}
# check preview parameter # check preview parameter
if ($options{'preview'}) { if ($options{'preview'}) {
$preview = 1; $preview = 1;
@ -227,7 +250,7 @@ $verbose = 1 if ($options{'verbose'});
# where am I? (1/2) # where am I? (1/2)
$0 =~ /^(.+[\\\/])[^\\\/]+[\\\/]*$/; $0 =~ /^(.+[\\\/])[^\\\/]+[\\\/]*$/;
my $run_dir = $1 || "."; $run_dir = $1 || ".";
$run_dir =~ s#/$##; # remove trailing slash $run_dir =~ s#/$##; # remove trailing slash
# don't do anything without configuration file(s) # don't do anything without configuration file(s)
@ -290,7 +313,7 @@ do_log ("INFO: runtime info: ".getpwuid ($<)."; ${hostname}\@${run_dir}; Perl v$
do_log ("INFO: reading 'alias' file ..."); do_log ("INFO: reading 'alias' file ...");
open (ALIASES, "<", "${run_dir}/alias") open (ALIASES, "<", "${run_dir}/alias")
or do_log ("ERROR: cannot read 'alias' file [$! $hostname]") and exit (1); or do_log ("ERROR: cannot read 'alias' file [$!/$hostname]") and exit (1);
while (<ALIASES>) { while (<ALIASES>) {
my ($key, $value, @values); my ($key, $value, @values);
@ -307,13 +330,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}}
$aliases{$key} = [resolve_aliases (join (",", @{$aliases{$key}}))]; my @new_array; my @filtered_array; # these are the working stashes
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");
@ -337,7 +391,7 @@ 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 =~ /^\./);
@ -355,7 +409,7 @@ if (-d "${run_dir}/fragments.d") {
# 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");
@ -412,7 +466,7 @@ foreach my $frag_file (@frag_files) {
# strip off path from file name for hash key # strip off path from file name for hash key
$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);
} }
@ -449,7 +503,7 @@ if ($? == 0) {
do_log ("INFO: reading 'grants' file ..."); do_log ("INFO: reading 'grants' file ...");
open (GRANTS, "<", "${run_dir}/grants") open (GRANTS, "<", "${run_dir}/grants")
or do_log ("ERROR: cannot read 'grants' file [$! $hostname]") and exit (1); or do_log ("ERROR: cannot read 'grants' file [$!/$hostname]") and exit (1);
while (<GRANTS>) { while (<GRANTS>) {
my ($what, $where, @what, @where); my ($what, $where, @what, @where);
@ -490,7 +544,7 @@ print Dumper(\@grants) if $debug;
if ($preview && $global) { if ($preview && $global) {
open (GRANTS, "<", "${run_dir}/grants") open (GRANTS, "<", "${run_dir}/grants")
or do_log ("ERROR: cannot read 'grants' file [$! $hostname]") and exit (1); or do_log ("ERROR: cannot read 'grants' file [$!/$hostname]") and exit (1);
while (<GRANTS>) { while (<GRANTS>) {
my ($what, $where, @what, @where); my ($what, $where, @what, @where);
@ -541,7 +595,7 @@ unless ($preview) {
# 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);
@ -554,7 +608,7 @@ while (my $frag_file = readdir (FRAGS_DIR)) {
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);
} }
} }
@ -571,7 +625,7 @@ foreach my $grant (@grants) {
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]")
and exit (1); and exit (1);
} }
print SUDO_FILE "$frags{$grant}\n" unless $preview; print SUDO_FILE "$frags{$grant}\n" unless $preview;
@ -606,7 +660,7 @@ unless ($preview) {
my $self_file = "$fragments_dir/$immutable_self_file"; my $self_file = "$fragments_dir/$immutable_self_file";
open (SELF_FILE, "+>", $self_file) open (SELF_FILE, "+>", $self_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]")
and exit (1); and exit (1);
print SELF_FILE "# THIS IS THE IMMUTABLE SELF FRAGMENT OF SUDO CONTROLS\n"; print SELF_FILE "# THIS IS THE IMMUTABLE SELF FRAGMENT OF SUDO CONTROLS\n";
@ -649,6 +703,7 @@ update_sudo.pl - distributes SUDO fragments according to a desired state model.
update_sudo.pl [-d|--debug] update_sudo.pl [-d|--debug]
[-h|--help] [-h|--help]
[-i|--ignore]
([-p|--preview] [-g|--global]) ([-p|--preview] [-g|--global])
[-v|--verbose] [-v|--verbose]
[-V|--version] [-V|--version]
@ -659,7 +714,7 @@ update_sudo.pl - distributes SUDO fragments according to a desired state model.
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. Orginally 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.
@ -683,6 +738,8 @@ Following settings must be configured:
=item * B<use_fqdn> : whether to use short or FQDN host names =item * B<use_fqdn> : whether to use short or FQDN host names
=item * B<ignore_errors> : whether to ignore errors during fragment deployment
=item * B<fragments_dir> : target directory for SUDO fragments files =item * B<fragments_dir> : target directory for SUDO fragments files
=item * B<visudo_bin> : path to the visudo tool (for sudo rules syntax checking) =item * B<visudo_bin> : path to the visudo tool (for sudo rules syntax checking)
@ -703,6 +760,10 @@ S< >Be I<very> verbose during execution; show array/hash dumps.
S< >Show the help page. S< >Show the help page.
=item -i | --ignore
S< >Ignore errors during fragment deployment.
=item -p | --preview =item -p | --preview
S< >Do not actually distribute any SUDO fragments, nor update/remove SUDO files. S< >Do not actually distribute any SUDO fragments, nor update/remove SUDO files.
@ -733,16 +794,4 @@ S< >Show version of the script.
=head1 AUTHOR =head1 AUTHOR
(c) KUDOS BVBA, Patrick Van der Veken (c) KUDOS BV, 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]