Skip to content

Commit

Permalink
Convert ssh blocker to an autofix
Browse files Browse the repository at this point in the history
Case RE-112: For sshd, the default value for PermitRootLogin
changes from "yes" to "no" when upgrading from CentOS 7 to a
CentOS 8 variant. We used to block if PermitRootLogin was not
explicitly set; but, instead it is more desirable to simply
emit a warning that we are going to set it to "yes" and go ahead
and set it so the behavior stays the same post upgrade. I've
altered the SSH blocker module to emit the warning and not block.
I've also moved the SSH blocker execution to happen earlier so
the warning is more visible.  And, I've added a component that
will provide the autofix for the sshd configuration file.

Changelog: Set PermitRootLogin for sshd to 'yes' if not explicitly
  set in the configuration file so that the behavior does not
  change after the upgrade.
  • Loading branch information
timmullin committed Feb 6, 2024
1 parent 552a6a5 commit b7860c2
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 54 deletions.
69 changes: 59 additions & 10 deletions elevate-cpanel
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ BEGIN { # Suppress load of all of these at earliest point.
$INC{'Elevate/Components/RpmDB.pm'} = 'script/elevate-cpanel.PL.static';
$INC{'Elevate/Components/RmMod.pm'} = 'script/elevate-cpanel.PL.static';
$INC{'Elevate/Components/WPToolkit.pm'} = 'script/elevate-cpanel.PL.static';
$INC{'Elevate/Components/SSH.pm'} = 'script/elevate-cpanel.PL.static';
$INC{'Elevate/Fetch.pm'} = 'script/elevate-cpanel.PL.static';
$INC{'Elevate/Logger.pm'} = 'script/elevate-cpanel.PL.static';
$INC{'Elevate/Motd.pm'} = 'script/elevate-cpanel.PL.static';
Expand Down Expand Up @@ -227,6 +228,7 @@ BEGIN { # Suppress load of all of these at earliest point.
IsContainer
ElevateScript
SSH
DiskSpace
WHM
Expand All @@ -235,7 +237,6 @@ BEGIN { # Suppress load of all of these at earliest point.
Databases
Repositories
SSH
JetBackup
NICs
EA4
Expand Down Expand Up @@ -1895,23 +1896,23 @@ EOS

sub check ($self) {

return $self->_blocker_invalid_ssh_config;
}

sub _blocker_invalid_ssh_config ($self) {
return $self->has_blocker(q[Issue with sshd configuration]) unless $self->_sshd_setup();
return 0;
return $self->_check_ssh_config();
}

sub _sshd_setup ($self) {
sub _check_ssh_config ($self) {
my $sshd_config = q[/etc/ssh/sshd_config];

my $setup = eval { File::Slurper::read_binary($sshd_config) } // '';
if ( my $exception = $@ ) {
ERROR("The system could not read the sshd config file ($sshd_config): $exception");
return $self->has_blocker(qq[Unable to read the sshd config file: $sshd_config]);
}

if ( $setup !~ m{^\s*PermitRootLogin\b}m ) {
ERROR( <<~"EOS" );
WARN( <<~"EOS" );
OpenSSH configuration file does not explicitly state the option PermitRootLogin in sshd_config file, which will default in RHEL8 to "prohibit-password".
Please set the 'PermitRootLogin' value in $sshd_config before upgrading.
We will set the 'PermitRootLogin' value in $sshd_config to 'yes' before upgrading.
EOS

return 0;
Expand Down Expand Up @@ -4085,6 +4086,52 @@ EOS

} # --- END lib/Elevate/Components/WPToolkit.pm

{ # --- BEGIN lib/Elevate/Components/SSH.pm

package Elevate::Components::SSH;

use cPstrict;

use Elevate::Constants ();

use Cwd ();
use File::Slurper ();

# use Log::Log4perl qw(:easy);
INIT { Log::Log4perl->import(qw{:easy}); }

# use Elevate::Components::Base();
our @ISA;
BEGIN { push @ISA, qw(Elevate::Components::Base); }

sub pre_leapp ($self) {

my $sshd_config = q[/etc/ssh/sshd_config];

my $setup = File::Slurper::read_binary($sshd_config) // '';

return if ( $setup =~ m{^\s*PermitRootLogin\b}m );

if ( $setup !~ m{\n$} && length $setup ) {
$setup .= "\n";
}

$setup .= "PermitRootLogin yes\n";

File::Slurper::write_binary( $sshd_config, $setup );

return;
}

sub post_leapp ($self) {

return;
}

1;

} # --- END lib/Elevate/Components/SSH.pm

{ # --- BEGIN lib/Elevate/Fetch.pm

package Elevate::Fetch;
Expand Down Expand Up @@ -5223,6 +5270,7 @@ use Elevate::Components::Repositories ();
use Elevate::Components::RpmDB ();
use Elevate::Components::RmMod ();
use Elevate::Components::WPToolkit ();
use Elevate::Components::SSH ();

use Elevate::Fetch ();
use Elevate::Logger ();
Expand Down Expand Up @@ -5923,6 +5971,7 @@ sub run_stage_2 ($self) {

Elevate::Motd->setup();

$self->run_component_once( 'SSH' => 'pre_leapp' );
$self->run_component_once( 'KernelCare' => 'pre_leapp' );
$self->run_component_once( 'Grub2' => 'pre_leapp' );

Expand Down
2 changes: 1 addition & 1 deletion lib/Elevate/Blockers.pm
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ our @BLOCKERS = qw{
IsContainer
ElevateScript
SSH
DiskSpace
WHM
Expand All @@ -59,7 +60,6 @@ our @BLOCKERS = qw{
Databases
Repositories
SSH
JetBackup
NICs
EA4
Expand Down
18 changes: 9 additions & 9 deletions lib/Elevate/Blockers/SSH.pm
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ use Log::Log4perl qw(:easy);

sub check ($self) {

return $self->_blocker_invalid_ssh_config;
return $self->_check_ssh_config();
}

sub _blocker_invalid_ssh_config ($self) {
return $self->has_blocker(q[Issue with sshd configuration]) unless $self->_sshd_setup();
return 0;
}

sub _sshd_setup ($self) {
sub _check_ssh_config ($self) {
my $sshd_config = q[/etc/ssh/sshd_config];

my $setup = eval { File::Slurper::read_binary($sshd_config) } // '';
if ( my $exception = $@ ) {
ERROR("The system could not read the sshd config file ($sshd_config): $exception");
return $self->has_blocker(qq[Unable to read the sshd config file: $sshd_config]);
}

if ( $setup !~ m{^\s*PermitRootLogin\b}m ) {
ERROR( <<~"EOS" );
WARN( <<~"EOS" );
OpenSSH configuration file does not explicitly state the option PermitRootLogin in sshd_config file, which will default in RHEL8 to "prohibit-password".
Please set the 'PermitRootLogin' value in $sshd_config before upgrading.
We will set the 'PermitRootLogin' value in $sshd_config to 'yes' before upgrading.
EOS

return 0;
Expand Down
51 changes: 51 additions & 0 deletions lib/Elevate/Components/SSH.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package Elevate::Components::SSH;

=encoding utf-8
=head1 NAME
Elevate::Components::SSH
Ensure that the sshd config file has 'PermitRootLogin' set to 'yes'
if it is not set.
=cut

use cPstrict;

use Elevate::Constants ();

use Cwd ();
use File::Slurper ();
use Log::Log4perl qw(:easy);

use parent qw{Elevate::Components::Base};

sub pre_leapp ($self) {

my $sshd_config = q[/etc/ssh/sshd_config];

my $setup = File::Slurper::read_binary($sshd_config) // '';

# PermitRootLogin is explicitly set, no need for changes
return if ( $setup =~ m{^\s*PermitRootLogin\b}m );

# Add ending newline if file does not end with newline
if ( $setup !~ m{\n$} && length $setup ) {
$setup .= "\n";
}

$setup .= "PermitRootLogin yes\n";

File::Slurper::write_binary( $sshd_config, $setup );

return;
}

sub post_leapp ($self) {

# Nothing to do
return;
}

1;
2 changes: 2 additions & 0 deletions script/elevate-cpanel.PL
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ use Elevate::Components::Repositories ();
use Elevate::Components::RpmDB ();
use Elevate::Components::RmMod ();
use Elevate::Components::WPToolkit ();
use Elevate::Components::SSH ();

use Elevate::Fetch ();
use Elevate::Logger ();
Expand Down Expand Up @@ -979,6 +980,7 @@ sub run_stage_2 ($self) {

Elevate::Motd->setup();

$self->run_component_once( 'SSH' => 'pre_leapp' );
$self->run_component_once( 'KernelCare' => 'pre_leapp' );
$self->run_component_once( 'Grub2' => 'pre_leapp' );

Expand Down
53 changes: 19 additions & 34 deletions t/blocker-SSH.t
Original file line number Diff line number Diff line change
Expand Up @@ -24,69 +24,54 @@ my $cpev = cpev->new;
my $ssh = $cpev->get_blocker('SSH');

{
note "checking _sshd_setup";
note "checking _check_ssh_config";

my $mock_sshd_cfg = Test::MockFile->file(q[/etc/ssh/sshd_config]);

my $sshd_error_message = <<~'EOS';
OpenSSH configuration file does not explicitly state the option PermitRootLogin in sshd_config file, which will default in RHEL8 to "prohibit-password".
Please set the 'PermitRootLogin' value in /etc/ssh/sshd_config before upgrading.
We will set the 'PermitRootLogin' value in /etc/ssh/sshd_config to 'yes' before upgrading.
EOS

is $ssh->_sshd_setup() => 0, "sshd_config does not exist";
message_seen( 'ERROR', $sshd_error_message );
my $blocker = $ssh->_check_ssh_config();
is ref $blocker, "cpev::Blocker", "sshd_config does not exist";
message_seen( 'ERROR', qr/The system could not read the sshd config file/ );
message_seen( 'WARN', qr/Elevation Blocker detected/ );

$mock_sshd_cfg->contents('');
is $ssh->_sshd_setup() => 0, "sshd_config with empty content";
message_seen( 'ERROR', $sshd_error_message );
is $ssh->_check_ssh_config() => 0, "sshd_config with empty content";
message_seen( 'WARN', $sshd_error_message );

$mock_sshd_cfg->contents( <<~EOS );
Fruit=cherry
Veggy=carrot
EOS
is $ssh->_sshd_setup() => 0, "sshd_config without PermitRootLogin option";
message_seen( 'ERROR', $sshd_error_message );
is $ssh->_check_ssh_config() => 0, "sshd_config without PermitRootLogin option";
message_seen( 'WARN', $sshd_error_message );

$mock_sshd_cfg->contents( <<~EOS );
Key=value
PermitRootLogin=yes
EOS
is $ssh->_sshd_setup() => 1, "sshd_config with PermitRootLogin=yes - multilines";
is $ssh->_check_ssh_config() => 1, "sshd_config with PermitRootLogin=yes - multilines";

$mock_sshd_cfg->contents(q[PermitRootLogin=no]);
is $ssh->_sshd_setup() => 1, "sshd_config with PermitRootLogin=no";
is $ssh->_check_ssh_config() => 1, "sshd_config with PermitRootLogin=no";

$mock_sshd_cfg->contents(q[PermitRootLogin no]);
is $ssh->_sshd_setup() => 1, "sshd_config with PermitRootLogin=no";
is $ssh->_check_ssh_config() => 1, "sshd_config with PermitRootLogin=no";

$mock_sshd_cfg->contents(q[PermitRootLogin = no]);
is $ssh->_sshd_setup() => 1, "sshd_config with PermitRootLogin = no";
is $ssh->_check_ssh_config() => 1, "sshd_config with PermitRootLogin = no";

$mock_sshd_cfg->contents(q[#PermitRootLogin=no]);
is $ssh->_sshd_setup() => 0, "sshd_config with commented PermitRootLogin=no";
message_seen( 'ERROR', $sshd_error_message );
is $ssh->_check_ssh_config() => 0, "sshd_config with commented PermitRootLogin=no";
message_seen( 'WARN', $sshd_error_message );

$mock_sshd_cfg->contents(q[#PermitRootLogin=yes]);
is $ssh->_sshd_setup() => 0, "sshd_config with commented PermitRootLogin=yes";
message_seen( 'ERROR', $sshd_error_message );
}

{
note "sshd setup check";

$ssh_mock->redefine( '_sshd_setup' => 0 );
is(
$ssh->_blocker_invalid_ssh_config(),
{
id => q[Elevate::Blockers::SSH::_blocker_invalid_ssh_config],
msg => 'Issue with sshd configuration',
},
q{Block if sshd is not explicitly configured.}
);

$ssh_mock->redefine( '_sshd_setup' => 1 );
is( $ssh->_blocker_invalid_ssh_config, 0, "no blocker if _sshd_setup is ok" );
$ssh_mock->unmock('_sshd_setup');
is $ssh->_check_ssh_config() => 0, "sshd_config with commented PermitRootLogin=yes";
message_seen( 'WARN', $sshd_error_message );
}

done_testing();
62 changes: 62 additions & 0 deletions t/components-SSH.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/local/cpanel/3rdparty/bin/perl

package test::cpev::components;

use FindBin;

use Test2::V0;
use Test2::Tools::Explain;
use Test2::Plugin::NoWarnings;
use Test2::Tools::Exception;

use Test::MockFile 0.032;
use Test::MockModule qw/strict/;

use lib $FindBin::Bin . "/lib";
use Test::Elevate;

use cPstrict;

my $ssh = bless {}, 'Elevate::Components::SSH';

{
note "checking pre_leapp";

my $mock_sshd_cfg = Test::MockFile->file(q[/etc/ssh/sshd_config]);

$mock_sshd_cfg->contents('');
$ssh->pre_leapp();
is $mock_sshd_cfg->contents(), "PermitRootLogin yes\n", 'Added PermitRootLogin to empty config';

my $pre_contents = "PasswordAuthentication no\nUseDNS no\nDenyGroups cpaneldemo\n";
$mock_sshd_cfg->contents($pre_contents);
$ssh->pre_leapp();
is $mock_sshd_cfg->contents(), $pre_contents . "PermitRootLogin yes\n", 'Added PermitRootLogin when ommited, trailing newline';

$pre_contents = "PasswordAuthentication no\nUseDNS no\nDenyGroups cpaneldemo";
$mock_sshd_cfg->contents($pre_contents);
$ssh->pre_leapp();
is $mock_sshd_cfg->contents(), $pre_contents . "\nPermitRootLogin yes\n", 'Added PermitRootLogin when ommited, no trailing newline';

$pre_contents = "PasswordAuthentication no\nPermitRootLogin yes\nUseDNS no\nDenyGroups cpaneldemo";
$mock_sshd_cfg->contents($pre_contents);
$ssh->pre_leapp();
is $mock_sshd_cfg->contents(), $pre_contents, 'Contents unchanged when PermitRootLogin present and active';

$pre_contents = "PermitRootLogin no\nPasswordAuthentication no\nUseDNS no\nDenyGroups cpaneldemo\n";
$mock_sshd_cfg->contents($pre_contents);
$ssh->pre_leapp();
is $mock_sshd_cfg->contents(), $pre_contents, 'Contents unchanged when PermitRootLogin present and active';

$pre_contents = "PasswordAuthentication no\n#PermitRootLogin yes\nUseDNS no\nDenyGroups cpaneldemo";
$mock_sshd_cfg->contents($pre_contents);
$ssh->pre_leapp();
is $mock_sshd_cfg->contents(), $pre_contents . "\nPermitRootLogin yes\n", 'Contents updated when PermitRootLogin present but commented out';

$pre_contents = "# PermitRootLogin no\nPasswordAuthentication no\nUseDNS no\nDenyGroups cpaneldemo\n";
$mock_sshd_cfg->contents($pre_contents);
$ssh->pre_leapp();
is $mock_sshd_cfg->contents(), $pre_contents . "PermitRootLogin yes\n", 'Contents updated when PermitRootLogin present but commented out';
}

done_testing();

0 comments on commit b7860c2

Please sign in to comment.