From bdb17828253957af7b88afb228f4218175fca4fc Mon Sep 17 00:00:00 2001 From: Travis Holloway Date: Mon, 17 Jun 2024 12:47:07 -0500 Subject: [PATCH] Use 'CloudLinux_8' target to backup EA4 profile with I360 installed Case RE-403: In RE-313, we added support for Imunify 360's hardened PHP repo to the EA4 blocker. Unfortunately, this improvement did not account for some packages that Imunify 360's hardened PHP repo does not provide which has resulted in some servers making it past the EA4 blocker that had packages installed that were not supported on A8/CL8. This change removes the current logic to support Imunify 360's hardened PHP repo, and replaces it by teaching 'Elevate::EA4::_backup_ea4_profile' to use the 'CloudLinux_8' target when Imunify 360 is installed with the hardened PHP feature. We can do this since Imunify 360's hardened PHP feature is essentially just providing CloudLinux's EA4 to servers with the feature available. Changelog: Use 'CloudLinux_8' target to backup EA4 profile on servers with Imunify 360 installed and providing the hardened PHP feature --- elevate-cpanel | 65 ++++++++++++++++------------------- lib/Elevate/Blockers/EA4.pm | 26 -------------- lib/Elevate/Components/EA4.pm | 11 ------ lib/Elevate/EA4.pm | 30 ++++++++++++++++ t/blocker-ea4.t | 52 ++-------------------------- t/components-ea4.t | 50 +++++++++------------------ 6 files changed, 79 insertions(+), 155 deletions(-) diff --git a/elevate-cpanel b/elevate-cpanel index 56a8c6dc..016094e9 100755 --- a/elevate-cpanel +++ b/elevate-cpanel @@ -1064,7 +1064,6 @@ EOS return unless scalar keys $dropped_pkgs->%*; my @incompatible; - my @imunify_pkgs; foreach my $pkg ( sort keys %$dropped_pkgs ) { my $type = $dropped_pkgs->{$pkg} // ''; next if $type eq 'exp'; # use of experimental packages is a non blocker @@ -1074,36 +1073,13 @@ EOS my $php_pkg = $1; next unless $self->_php_version_is_in_use($php_pkg); - if ( $self->_pkg_is_provided_by_imunify_360($php_pkg) ) { - push @imunify_pkgs, $pkg; - next; - } - } - - if ( $pkg eq 'ea-profiles-cloudlinux' && $self->_pkg_is_provided_by_imunify_360($pkg) ) { - push @imunify_pkgs, $pkg; - next; } - push @incompatible, $pkg; } - if (@imunify_pkgs) { - Elevate::StageFile::remove_from_stage_file('ea4_imunify_packages'); - Elevate::StageFile::update_stage_file( { ea4_imunify_packages => \@imunify_pkgs } ); - } - return @incompatible; } - sub _pkg_is_provided_by_imunify_360 ( $self, $pkg ) { - return 0 unless -x Elevate::Constants::IMUNIFY_AGENT; - - my $version = Cpanel::Pkgr::get_package_version($pkg); - - return $version =~ m/cloudlinux/ ? 1 : 0; - } - sub _php_version_is_in_use ( $self, $php ) { my $current_php_usage = $self->_get_php_versions_in_use(); @@ -3266,7 +3242,6 @@ EOS $self->run_once('_restore_ea4_profile'); $self->run_once('_restore_ea_addons'); - $self->run_once('_restore_imunify_phps'); $self->run_once('_restore_config_files'); @@ -3373,16 +3348,6 @@ EOS return; } - sub _restore_imunify_phps ($self) { - - my $php_pkgs = Elevate::StageFile::read_stage_file('ea4_imunify_packages'); - return unless ref $php_pkgs eq 'ARRAY'; - return unless scalar $php_pkgs->@*; - - $self->dnf->install(@$php_pkgs); - return; - } - sub _ensure_sites_use_correct_php_version ($self) { my $vhost_versions = Elevate::StageFile::read_stage_file('php_get_vhost_versions'); @@ -5712,6 +5677,7 @@ EOS use File::Temp (); + use Elevate::Constants (); use Elevate::OS (); use Elevate::StageFile (); @@ -5723,6 +5689,8 @@ EOS # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } + use constant IMUNIFY_AGENT => Elevate::Constants::IMUNIFY_AGENT; + sub backup ( $check_mode = 0 ) { Elevate::EA4::_backup_ea4_profile($check_mode); Elevate::EA4::_backup_ea_addons(); @@ -5755,10 +5723,37 @@ EOS return; } + sub _imunify360_is_installed_and_provides_hardened_php () { + return 0 unless -x IMUNIFY_AGENT; + + my $out = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{version --json} ); + my $license_data = eval { Cpanel::JSON::Load($out) } // {}; + + return 0 unless ref $license_data->{license}; + + if ( $license_data->{'license'}->{'license_type'} eq 'imunify360' ) { + + my $output = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{features list} ); + my @features = map { + my $trim_spaces = $_; + $trim_spaces =~ s/\s+//g; + $trim_spaces; + } grep { m/\S/ } split( "\n", $output ); + + foreach my $feature (@features) { + return 1 if $feature eq 'hardened-php'; + } + } + + return 0; + } + sub _get_ea4_profile ($check_mode) { my $ea_alias = Elevate::OS::ea_alias(); + $ea_alias = 'CloudLinux_8' if Elevate::EA4::_imunify360_is_installed_and_provides_hardened_php(); + my @cmd = ( '/usr/local/bin/ea_current_to_profile', "--target-os=$ea_alias" ); my $profile_file; diff --git a/lib/Elevate/Blockers/EA4.pm b/lib/Elevate/Blockers/EA4.pm index c0fcb732..5d110d74 100644 --- a/lib/Elevate/Blockers/EA4.pm +++ b/lib/Elevate/Blockers/EA4.pm @@ -67,7 +67,6 @@ sub _get_incompatible_packages ($self) { return unless scalar keys $dropped_pkgs->%*; my @incompatible; - my @imunify_pkgs; foreach my $pkg ( sort keys %$dropped_pkgs ) { my $type = $dropped_pkgs->{$pkg} // ''; next if $type eq 'exp'; # use of experimental packages is a non blocker @@ -77,38 +76,13 @@ sub _get_incompatible_packages ($self) { my $php_pkg = $1; next unless $self->_php_version_is_in_use($php_pkg); - if ( $self->_pkg_is_provided_by_imunify_360($php_pkg) ) { - push @imunify_pkgs, $pkg; - next; - } } - - if ( $pkg eq 'ea-profiles-cloudlinux' && $self->_pkg_is_provided_by_imunify_360($pkg) ) { - push @imunify_pkgs, $pkg; - next; - } - push @incompatible, $pkg; } - if (@imunify_pkgs) { - Elevate::StageFile::remove_from_stage_file('ea4_imunify_packages'); - Elevate::StageFile::update_stage_file( { ea4_imunify_packages => \@imunify_pkgs } ); - } - return @incompatible; } -sub _pkg_is_provided_by_imunify_360 ( $self, $pkg ) { - return 0 unless -x Elevate::Constants::IMUNIFY_AGENT; - - my $version = Cpanel::Pkgr::get_package_version($pkg); - - # If the package is coming from CL, then we can assume - # that it is provided by Imunify 360 at this point - return $version =~ m/cloudlinux/ ? 1 : 0; -} - sub _php_version_is_in_use ( $self, $php ) { my $current_php_usage = $self->_get_php_versions_in_use(); diff --git a/lib/Elevate/Components/EA4.pm b/lib/Elevate/Components/EA4.pm index a8b271ba..9d557e88 100644 --- a/lib/Elevate/Components/EA4.pm +++ b/lib/Elevate/Components/EA4.pm @@ -35,7 +35,6 @@ sub post_leapp ($self) { $self->run_once('_restore_ea4_profile'); $self->run_once('_restore_ea_addons'); - $self->run_once('_restore_imunify_phps'); # This needs to happen after EA4 has been reinstalled # @@ -155,16 +154,6 @@ sub _restore_config_files ($self) { return; } -sub _restore_imunify_phps ($self) { - - my $php_pkgs = Elevate::StageFile::read_stage_file('ea4_imunify_packages'); - return unless ref $php_pkgs eq 'ARRAY'; - return unless scalar $php_pkgs->@*; - - $self->dnf->install(@$php_pkgs); - return; -} - sub _ensure_sites_use_correct_php_version ($self) { my $vhost_versions = Elevate::StageFile::read_stage_file('php_get_vhost_versions'); diff --git a/lib/Elevate/EA4.pm b/lib/Elevate/EA4.pm index 5e2c8da1..e864896b 100644 --- a/lib/Elevate/EA4.pm +++ b/lib/Elevate/EA4.pm @@ -14,6 +14,7 @@ use cPstrict; use File::Temp (); +use Elevate::Constants (); use Elevate::OS (); use Elevate::StageFile (); @@ -24,6 +25,8 @@ use Cpanel::SafeRun::Simple (); use Log::Log4perl qw(:easy); +use constant IMUNIFY_AGENT => Elevate::Constants::IMUNIFY_AGENT; + sub backup ( $check_mode = 0 ) { Elevate::EA4::_backup_ea4_profile($check_mode); Elevate::EA4::_backup_ea_addons(); @@ -57,10 +60,37 @@ sub _backup_ea4_profile ($check_mode) { return; } +sub _imunify360_is_installed_and_provides_hardened_php () { + return 0 unless -x IMUNIFY_AGENT; + + my $out = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{version --json} ); + my $license_data = eval { Cpanel::JSON::Load($out) } // {}; + + return 0 unless ref $license_data->{license}; + + if ( $license_data->{'license'}->{'license_type'} eq 'imunify360' ) { + + my $output = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{features list} ); + my @features = map { + my $trim_spaces = $_; + $trim_spaces =~ s/\s+//g; + $trim_spaces; + } grep { m/\S/ } split( "\n", $output ); + + foreach my $feature (@features) { + return 1 if $feature eq 'hardened-php'; + } + } + + return 0; +} + sub _get_ea4_profile ($check_mode) { my $ea_alias = Elevate::OS::ea_alias(); + $ea_alias = 'CloudLinux_8' if Elevate::EA4::_imunify360_is_installed_and_provides_hardened_php(); + my @cmd = ( '/usr/local/bin/ea_current_to_profile', "--target-os=$ea_alias" ); my $profile_file; diff --git a/t/blocker-ea4.t b/t/blocker-ea4.t index ccb69185..f949857c 100644 --- a/t/blocker-ea4.t +++ b/t/blocker-ea4.t @@ -155,8 +155,7 @@ Please remove these packages before continuing the update. }, "blocker with expected error" or diag explain $blocker; $mock_ea4->redefine( - _php_version_is_in_use => 1, - _pkg_is_provided_by_imunify_360 => 0, + _php_version_is_in_use => 1, ); $stage_ea4->{'dropped_pkgs'} = { @@ -185,8 +184,7 @@ Please remove these packages before continuing the update. or diag explain $blocker; $mock_ea4->redefine( - _php_version_is_in_use => 0, - _pkg_is_provided_by_imunify_360 => 0, + _php_version_is_in_use => 0, ); $stage_ea4->{'dropped_pkgs'} = { @@ -196,52 +194,6 @@ Please remove these packages before continuing the update. ok !$ea4->_blocker_ea4_profile(), 'No blocker when dropped package is an ea-php version that is not in use'; ea_info_check($target_os); - $mock_ea4->redefine( - _php_version_is_in_use => 0, - _pkg_is_provided_by_imunify_360 => 1, - ); - - ok !$ea4->_blocker_ea4_profile(), 'No blocker when dropped package is an ea-php version that is in use but provided by Imunify 360'; - ea_info_check($target_os); - - is( - $update_stage_file_data, - {}, - 'No ea-php packages need to be installed for Imunify 360 when the PHP version is not in use' - ) or diag explain $update_stage_file_data; - - $stage_ea4->{'dropped_pkgs'} = { - 'ea-php42' => 'reg', - 'ea-profiles-cloudlinux' => 'reg', - }; - - ok !$ea4->_blocker_ea4_profile(), 'No blocker when dropped package is "ea-profiles-cloudlinux" and provided by Imunify 360'; - ea_info_check($target_os); - - is( - $update_stage_file_data, - { - ea4_imunify_packages => ['ea-profiles-cloudlinux'], - }, - 'No blocker for ea-profiles-cloudlinux provided by Imunify 360' - ); - - $mock_ea4->redefine( - _php_version_is_in_use => 1, - _pkg_is_provided_by_imunify_360 => 1, - ); - - ok !$ea4->_blocker_ea4_profile(), 'No blocker when dropped package is an ea-php version that is in use but provided by Imunify 360'; - ea_info_check($target_os); - - is( - $update_stage_file_data, - { - ea4_imunify_packages => [ 'ea-php42', 'ea-profiles-cloudlinux' ], - }, - 'No ea-php packages need to be installed for Imunify 360 when the PHP version is not in use' - ) or diag explain $update_stage_file_data; - $stage_ea4 = {}; } diff --git a/t/components-ea4.t b/t/components-ea4.t index 05edbacf..75eb392e 100644 --- a/t/components-ea4.t +++ b/t/components-ea4.t @@ -45,7 +45,8 @@ sub startup : Test(startup) ($self) { $stage_file = Test::MockFile->file( Elevate::StageFile::ELEVATE_STAGE_FILE() ); - $self->{mock_profile} = Test::MockFile->file(PROFILE_FILE); + $self->{mock_profile} = Test::MockFile->file(PROFILE_FILE); + $self->{mock_imunify_agent} = Test::MockFile->file( Elevate::EA4::IMUNIFY_AGENT() ); $self->{mock_httpd} = Test::MockModule->new('Cpanel::Config::Httpd'); @@ -198,7 +199,7 @@ EOS return; } -sub test_get_ea4_profile_check_mode : Test(14) ($self) { +sub test_get_ea4_profile_check_mode : Test(19) ($self) { for my $os ( 'cent', 'cloud' ) { set_os_to($os); @@ -232,6 +233,20 @@ sub test_get_ea4_profile_check_mode : Test(14) ($self) { my $expected_target = $os eq 'cent' ? 'CentOS_8' : 'CloudLinux_8'; message_seen( 'INFO' => "Running: /usr/local/bin/ea_current_to_profile --target-os=$expected_target --output=$expected_profile" ); message_seen( 'INFO' => "Backed up EA4 profile to $expected_profile" ); + + # The expected target is CloudLinux_8 when Imunify 360 provides + # hardened PHP + if ( $os eq 'cent' ) { + my $mock_elevate_ea4 = Test::MockModule->new('Elevate::EA4'); + $mock_elevate_ea4->redefine( + _imunify360_is_installed_and_provides_hardened_php => 1, + ); + + is( Elevate::EA4::_get_ea4_profile(1), $expected_profile, "_get_ea4_profile uses a temporary file for the profile" ); + + message_seen( 'INFO' => "Running: /usr/local/bin/ea_current_to_profile --target-os=CloudLinux_8 --output=$expected_profile" ); + message_seen( 'INFO' => "Backed up EA4 profile to $expected_profile" ); + } } return; @@ -501,37 +516,6 @@ sub test_backup_and_restore_config_files : Test(10) ($self) { return; } -sub test__restore_imunify_phps : Test(4) ($self) { - - my $mock_stagefile = Test::MockModule->new('Elevate::StageFile'); - $mock_stagefile->redefine( - read_stage_file => [], - ); - - my $ssystem_cmd; - my $mock_elevate = Test::MockModule->new('cpev'); - $mock_elevate->redefine( - ssystem_and_die => sub ( $, @args ) { - $ssystem_cmd = join( ' ', @args ); - return; - }, - ); - - my $ea4 = cpev->new->component('EA4'); - - is( $ea4->_restore_imunify_phps(), undef, 'returns undef' ); - is( $ssystem_cmd, undef, 'No commands are called when there is nothing to restore' ); - - $mock_stagefile->redefine( - read_stage_file => [ 'ea-foo', 'ea-bar' ], - ); - - is( $ea4->_restore_imunify_phps(), undef, 'returns undef' ); - is( $ssystem_cmd, '/usr/bin/dnf -y install ea-foo ea-bar', 'The correct command is called when there are packages to install' ); - - return; -} - sub test__ensure_sites_use_correct_php_version : Test(11) ($self) { my $mock_stagefile = Test::MockModule->new('Elevate::StageFile');