Compare commits
27 Commits
sudoers_ow
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
29322b6a48 | ||
78d2d6aff1 | |||
813315bc4f | |||
6e1af4dd02 | |||
518e049a12 | |||
|
0ceb4120be | ||
e601284bc8 | |||
|
1c0d1139aa | ||
|
a7ee16d454 | ||
|
330e413b98 | ||
|
af291c07e0 | ||
|
0adabe65cc | ||
|
e5e2981d88 | ||
|
465c2cec90 | ||
|
7aee2b336d | ||
|
af9ed19d6b | ||
|
4e464ffeb0 | ||
|
0ef4fa6dd9 | ||
|
73f0f146d1 | ||
|
dc86b054aa | ||
|
09a1b75b3c | ||
|
554661e502 | ||
|
50c62d7a4b | ||
|
787966ff39 | ||
|
e38facb5d9 | ||
|
2a878a12bb | ||
|
33f2c00db0 |
24
README.md
24
README.md
@ -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)*
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
1403
manage_sudo.sh
1403
manage_sudo.sh
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
|
||||||
|
141
update_sudo.pl
141
update_sudo.pl
@ -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}}
|
||||||
|
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");
|
||||||
@ -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]
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user