Skip to content
1 change: 1 addition & 0 deletions bin/rex
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ The C<rex> script can be used to execute tasks defined in a Rexfile from the com
-O Pass additional options, like CMDB path
-s Use sudo for every command
-S Password for sudo
-D Use doas for every command
-t Number of threads to use (aka 'parallelism' param)
-v Display (R)?ex version

Expand Down
63 changes: 59 additions & 4 deletions lib/Rex.pm
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,13 @@ BEGIN {
use File::Basename;
use File::Spec;
eval { Net::SSH2->require; };

}

our ( @EXPORT, @CONNECTION_STACK, $GLOBAL_SUDO, $MODULE_PATHS,
$WITH_EXIT_STATUS, @FEATURE_FLAGS );
our (
@EXPORT, @CONNECTION_STACK, $GLOBAL_SUDO, $GLOBAL_DOAS,
$MODULE_PATHS, $WITH_EXIT_STATUS, @FEATURE_FLAGS
);

$WITH_EXIT_STATUS = 1; # since 0.50 activated by default
@FEATURE_FLAGS = ();
Expand Down Expand Up @@ -241,8 +244,10 @@ sub push_connection {
}

sub pop_connection {
pop @CONNECTION_STACK;
Rex::Logger::debug( "Connections in queue: " . scalar(@CONNECTION_STACK) );
my $connection = pop @CONNECTION_STACK;

Rex::Logger::debug( 'Connections in queue: ' . scalar @CONNECTION_STACK );
return $connection;
}

sub reconnect_lost_connections {
Expand Down Expand Up @@ -385,6 +390,56 @@ sub global_sudo {
Rex::Config->set_use_cache(1);
}

=head2 is_doas

Returns 1 if the current operation is executed within doas.

=cut

sub is_doas {
my $connection;

if (@CONNECTION_STACK) {
$connection = $CONNECTION_STACK[-1]; ## no critic (TooMuchCode::ProhibitDuplicateLiteral)
}

if ($connection) {
if ( exists $connection->{server}->{auth}->{doas}
&& $connection->{server}->{auth}->{doas} == 1 )
{
return 1;
}
elsif ( exists $connection->{server}->{auth}->{doas}
&& $connection->{server}->{auth}->{doas} == 0 )
{
return 0;
}
}

if ($GLOBAL_DOAS) { return 1; }

if ($connection) {
return $connection->{conn}->get_current_use_doas;
}

return 0;
}

=head2 global_doas

Enable or disable doas globally.

=cut

sub global_doas {
my ($on) = @_;
$GLOBAL_DOAS = $on;

# turn cache on
Rex::Config->set_use_cache(1);
return 1;
}

=head2 get_sftp

Returns the sftp object for the current ssh connection.
Expand Down
14 changes: 13 additions & 1 deletion lib/Rex/CLI.pm
Original file line number Diff line number Diff line change
Expand Up @@ -280,19 +280,28 @@ CHECK_OVERWRITE: {
_handle_T(%opts);

Rex::global_sudo(0);
Rex::global_doas(0);
Rex::Logger::debug("Removing lockfile") if ( !exists $opts{'F'} );
CORE::unlink("$::rexfile.lock") if ( !exists $opts{'F'} );
CORE::exit 0;
}

my $enabled = "on";

# turn sudo on with cli option s is used
if ( exists $opts{'s'} ) {
sudo("on");
sudo($enabled);
}

if ( exists $opts{'S'} ) {
sudo_password( $opts{'S'} );
}

# turn doas on with cli option D is used
if ( exists $opts{'D'} ) {
doas($enabled);
}

if ( exists $opts{'t'} ) {
parallelism( $opts{'t'} );
}
Expand Down Expand Up @@ -439,6 +448,7 @@ sub __help__ {
printf $fmt, "-O", "Pass additional options, like CMDB path";
printf $fmt, "-s", "Use sudo for every command";
printf $fmt, "-S", "Password for sudo";
printf $fmt, "-D", 'Use doas for every command';
printf $fmt, "-t", "Number of threads to use (aka 'parallelism' param)";
printf $fmt, "-v", "Display (R)?ex version";
print "\n";
Expand Down Expand Up @@ -649,6 +659,7 @@ sub handle_lock_file {
else {
Rex::Logger::debug("Found stale lock file. Removing it.");
Rex::global_sudo(0);
Rex::global_doas(0);
CORE::unlink("$rexfile.lock");
}
}
Expand Down Expand Up @@ -813,6 +824,7 @@ sub exit_rex {
summarize($signal) if !$signal;

Rex::global_sudo(0);
Rex::global_doas(0);
Rex::Logger::debug("Removing lockfile") if !exists $opts{'F'};
unlink("$::rexfile.lock") if !exists $opts{'F'};

Expand Down
3 changes: 3 additions & 0 deletions lib/Rex/Commands.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,9 @@ sub LOCAL (&) {
my $local_connect = Rex::Interface::Connection->create("Local");

my $old_global_sudo = $Rex::GLOBAL_SUDO;
my $old_global_doas = $Rex::GLOBAL_DOAS;
$Rex::GLOBAL_SUDO = 0;
$Rex::GLOBAL_DOAS = 0;

Rex::push_connection(
{
Expand All @@ -1230,6 +1232,7 @@ sub LOCAL (&) {
Rex::pop_connection();

$Rex::GLOBAL_SUDO = $old_global_sudo;
$Rex::GLOBAL_DOAS = $old_global_doas;

return $ret;
}
Expand Down
126 changes: 104 additions & 22 deletions lib/Rex/Commands/Run.pm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ With this module you can run a command.

my $output = run 'ls -l';
sudo 'id';
doas 'id';

=head1 CONFIGURATION AND ENVIRONMENT

Expand Down Expand Up @@ -58,6 +59,11 @@ use Rex::Interface::Exec;
use Rex::Interface::Fs;
use English qw(-no_match_vars);

my $ref_code = 'CODE';
my $ref_hash = 'HASH';
my $on = 'on';
my $on_flag = '-on';

BEGIN {
if ( $^O !~ m/^MSWin/ ) {
eval "use Expect";
Expand All @@ -71,7 +77,7 @@ BEGIN {
use vars qw(@EXPORT);
use base qw(Rex::Exporter);

@EXPORT = qw(run can_run sudo);
@EXPORT = qw(run can_run sudo doas);

=head2 run($command [, $callback], %options)

Expand Down Expand Up @@ -203,10 +209,13 @@ sub run {
my $cmd = shift;

if ( ref $cmd eq "ARRAY" ) {
my $ret;

for my $_cmd ( @{$cmd} ) {
&run( $_cmd, @_ );
run( $_cmd, @_ );
}
return;

return $ret;
}

my ( $code, $option );
Expand Down Expand Up @@ -244,8 +253,8 @@ sub run {
run( $option->{command} );
}
);

return;
$res_cmd = undef;
return $res_cmd;
}

if ( exists $option->{command} ) {
Expand Down Expand Up @@ -349,7 +358,7 @@ sub run {
}

if ($code) {
$out_ret = &$code( $out, $err );
$out_ret = $code->( $out, $err );
}

else {
Expand Down Expand Up @@ -458,37 +467,110 @@ sub sudo {
my ($cmd) = @_;

my $options;
if ( ref $cmd eq "HASH" ) {
my $ret;

if ( ref $cmd eq $ref_hash ) {
$options = $cmd;
$cmd = $options->{command};
}

if ( $cmd eq $on || $cmd eq $on_flag || $cmd eq '1' ) {
Rex::Logger::debug('Turning sudo globally on');
Rex::global_doas(1);
}
elsif ( $cmd eq '0' ) {
Rex::Logger::debug('Turning sudo globally off');
Rex::global_doas(0);
}
else {
Rex::get_current_connection_object()->push_use_doas(1);
Rex::get_current_connection_object()->push_doas_options( %{$options} );

if ( ref $cmd eq $ref_code ) {
$ret = $cmd->();
}
else {
$ret = i_run( $cmd, fail_ok => 1 );
}

Rex::get_current_connection_object()->pop_use_doas();
Rex::get_current_connection_object()->pop_doas_options();
}

return $ret;
}

=head2 doas($command)

This function will execute the given command with doas.

With this function you can run a command as another user via doas.

B<Note:> doas on OpenBSD does not support password input via stdin like sudo does.
You must configure F</etc/doas.conf> appropriately for unattended execution.
A typical configuration for a user to run commands as root without a password would be:

permit nopass myuser as root

However, administrators should restrict rules appropriately for their security requirements.

You can also pass a hash reference as first argument to specify options:

doas { user => 'root', command => 'id' };

doas supports the following options:

=over 4

=item user

The user to execute the command as.

=item command

The command to execute.

=back

To use doas without a password prompt (non-interactively), Rex uses the C<-n> option.
Missing authorization will cause an immediate command failure.

=cut

sub doas {
my ($cmd) = @_;

my $options = {};

if ( ref $cmd eq $ref_hash ) {
$options = $cmd;
$cmd = $options->{command};
}

if ( $cmd eq "on" || $cmd eq "-on" || $cmd eq "1" ) {
Rex::Logger::debug("Turning sudo globally on");
Rex::global_sudo(1);
return;
if ( $cmd eq $on || $cmd eq $on_flag || $cmd eq '1' ) {
Rex::Logger::debug('Turning doas globally on');
return Rex::global_doas(1);
}
elsif ( $cmd eq "0" ) {
Rex::Logger::debug("Turning sudo globally off");
Rex::global_sudo(0);
return;
elsif ( $cmd eq '0' ) {
Rex::Logger::debug('Turning doas globally off');
return Rex::global_doas(0);
}

Rex::get_current_connection_object()->push_use_sudo(1);
Rex::get_current_connection_object()->push_sudo_options( %{$options} );
Rex::get_current_connection_object()->push_use_doas(1);
Rex::get_current_connection_object()->push_doas_options( %{$options} );

my $ret;

# if sudo is used with a code block
if ( ref($cmd) eq "CODE" ) {
$ret = &$cmd();
# if doas is used with a code block
if ( ref $cmd eq $ref_code ) {
$ret = $cmd->();
}
else {
$ret = i_run( $cmd, fail_ok => 1 );
}

Rex::get_current_connection_object()->pop_use_sudo();
Rex::get_current_connection_object()->pop_sudo_options();
Rex::get_current_connection_object()->pop_use_doas();
Rex::get_current_connection_object()->pop_doas_options();

return $ret;
}
Expand Down
16 changes: 15 additions & 1 deletion lib/Rex/Group/Entry/Server.pm
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ sub new {
delete $self->{sudo_password};
}

if ( $self->{doas} ) {
$self->{auth}->{doas} = $self->{doas};
delete $self->{doas};
}

if ( $self->{auth_type} ) {
$self->{auth}->{auth_type} = $self->{auth_type};
delete $self->{auth_type};
Expand Down Expand Up @@ -284,12 +289,21 @@ sub get_sudo_password {
Rex::Config->get_sudo_password;
}

sub get_doas {
my ($self) = @_;
if ( exists $self->{auth}->{doas} ) {
return $self->{auth}->{doas};
}

return 0;
}

sub merge_auth {
my ( $self, $other_auth ) = @_;

my %new_auth;
my @keys =
qw/user password port private_key public_key auth_type sudo sudo_password/;
qw/user password port private_key public_key auth_type sudo sudo_password doas/;

for my $key (@keys) {
my $call = "get_$key";
Expand Down
Loading
Loading