mirror of
https://github.com/webmin/webmin.git
synced 2026-06-27 22:40:26 +01:00
Fix to gate packaged unit deletion behind config
ⓘ Add a disabled-by-default module option for deleting packaged systemd unit files, while keeping local unit deletion allowed and enforcing the policy in both UI and backend paths.
This commit is contained in:
@@ -5,6 +5,7 @@ visible_tabs=service,timer,socket,path,target,storage,resources,device,user
|
||||
show_runtime_units=1
|
||||
default_create_scope=system
|
||||
manual_vendor_units=1
|
||||
delete_vendor_units=0
|
||||
default_linger=1
|
||||
show_unit_suffixes=0
|
||||
show_dropin_inventory=1
|
||||
|
||||
@@ -5,6 +5,7 @@ visible_tabs=Tabs to show on the index page,15,visible_tabs
|
||||
show_runtime_units=Show generated and transient units,1,1-Yes,0-No
|
||||
default_create_scope=Default scope for new units,1,system-System units,user-User units
|
||||
manual_vendor_units=Include vendor unit files in the manual editor,1,1-Yes,0-No
|
||||
delete_vendor_units=Allow deleting packaged unit files,1,1-Yes,0-No
|
||||
default_linger=Enable linger by default for new user units,1,1-Yes,0-No
|
||||
show_unit_suffixes=Show full unit names with type suffixes,1,1-Yes,0-No
|
||||
show_dropin_inventory=Show drop-in override inventory,1,1-Yes,0-No
|
||||
|
||||
@@ -1087,7 +1087,8 @@ else {
|
||||
[ 'delete_override',
|
||||
$text{'edit_deleteoverridenow'} || "Delete Override" ]);
|
||||
}
|
||||
elsif ($unit_file_editable && $in{'name'} ne 'webmin.service' &&
|
||||
elsif (($edit_user_scope || system_unit_file_deletable($u)) &&
|
||||
$unit_file_editable && $in{'name'} ne 'webmin.service' &&
|
||||
systemd_can_delete($edit_user_scope, $unituser)) {
|
||||
push(@delete_buttons, [ 'delete', $text{'delete'} ]);
|
||||
}
|
||||
|
||||
9
systemd/help/config_delete_vendor_units.html
Normal file
9
systemd/help/config_delete_vendor_units.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<header>Allow deleting packaged unit files</header>
|
||||
<p>Controls whether the module may delete unit files from package-managed
|
||||
vendor directories such as <tt>/usr/lib/systemd/system</tt> or
|
||||
<tt>/lib/systemd/system</tt>. This is disabled by default because package
|
||||
updates may restore or expect those files.</p>
|
||||
|
||||
<p>When set to no, only local administrator-created unit files under
|
||||
<tt>/etc/systemd/system</tt> can be deleted. Packaged units can still be
|
||||
disabled, masked, or customized with drop-in overrides.</p>
|
||||
@@ -217,6 +217,7 @@ systemd_title2_user=Edit Systemd User Unit
|
||||
systemd_title2_view=View Systemd Unit
|
||||
systemd_title2_view_user=View Systemd User Unit
|
||||
systemd_egone=Unit no longer exists!
|
||||
systemd_elocaldelete=Deleting packaged unit files is disabled in the module configuration. Disable, mask or create a drop-in override, or enable packaged unit deletion first.
|
||||
systemd_header=Systemd unit details
|
||||
systemd_name=Unit name
|
||||
systemd_type=Unit type
|
||||
|
||||
@@ -32,6 +32,9 @@ $config{"default_create_scope"} = "system"
|
||||
$config{"default_create_scope"} !~ /^(system|user)$/);
|
||||
$config{"manual_vendor_units"} = 1
|
||||
if (!defined($config{"manual_vendor_units"}));
|
||||
$config{"delete_vendor_units"} = 0
|
||||
if (!defined($config{"delete_vendor_units"}) ||
|
||||
$config{"delete_vendor_units"} !~ /^[01]$/);
|
||||
$config{"default_linger"} = 1
|
||||
if (!defined($config{"default_linger"}));
|
||||
$config{"show_unit_suffixes"} = 0
|
||||
@@ -2853,7 +2856,7 @@ Returns true if a system unit root is the local administrator directory.
|
||||
sub local_unit_file_root
|
||||
{
|
||||
my ($root) = @_;
|
||||
return $root eq "/etc/systemd/system";
|
||||
return $root eq get_local_unit_root();
|
||||
}
|
||||
|
||||
=head2 get_system_unit_file_root_candidates()
|
||||
@@ -2863,7 +2866,7 @@ Returns possible systemd unit directories before existence and symlink checks.
|
||||
=cut
|
||||
sub get_system_unit_file_root_candidates
|
||||
{
|
||||
return ("/etc/systemd/system",
|
||||
return (get_local_unit_root(),
|
||||
"/usr/lib/systemd/system",
|
||||
"/lib/systemd/system");
|
||||
}
|
||||
@@ -3417,18 +3420,23 @@ return reload_user_manager($user);
|
||||
|
||||
=head2 delete_system_unit(name)
|
||||
|
||||
Delete all traces of some systemd unit.
|
||||
Delete a permitted systemd unit file.
|
||||
|
||||
=cut
|
||||
sub delete_system_unit
|
||||
{
|
||||
my ($name) = @_;
|
||||
return (0, $text{'systemd_ename'}) if (!valid_unit_name($name));
|
||||
my $file = get_unit_root($name)."/".$name;
|
||||
return (0, $text{'systemd_egone'}) if (!-e $file && !-l $file);
|
||||
unlink_logged($file);
|
||||
reload_manager();
|
||||
return (1, "");
|
||||
foreach my $root (get_system_unit_file_root_candidates()) {
|
||||
my $file = $root."/".$name;
|
||||
next if (!-e $file && !-l $file);
|
||||
return (0, $text{'systemd_elocaldelete'})
|
||||
if (!system_unit_root_delete_allowed($root));
|
||||
unlink_logged($file);
|
||||
reload_manager();
|
||||
return (1, "");
|
||||
}
|
||||
return (0, $text{'systemd_egone'});
|
||||
}
|
||||
|
||||
=head2 get_unit_types()
|
||||
@@ -3558,6 +3566,48 @@ return 0 if ($unit->{'file'} =~ m{/systemd/(transient|generator)/});
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 system_unit_file_deletable(&unit)
|
||||
|
||||
Returns 1 if a system unit record points to a unit file that can be removed
|
||||
under the current module configuration.
|
||||
|
||||
=cut
|
||||
sub system_unit_file_deletable
|
||||
{
|
||||
my ($unit) = @_;
|
||||
return 0 if (!unit_file_editable($unit));
|
||||
return system_unit_file_delete_allowed($unit->{'file'}, $unit->{'name'});
|
||||
}
|
||||
|
||||
=head2 system_unit_file_delete_allowed(file, name)
|
||||
|
||||
Returns 1 if a system unit path is in a root where deleting unit files is
|
||||
currently permitted.
|
||||
|
||||
=cut
|
||||
sub system_unit_file_delete_allowed
|
||||
{
|
||||
my ($file, $name) = @_;
|
||||
return 0 if (!$file || !$name || !valid_unit_name($name));
|
||||
foreach my $root (get_system_unit_file_root_candidates()) {
|
||||
next if (!system_unit_root_delete_allowed($root));
|
||||
return 1 if ($file eq $root."/".$name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
=head2 system_unit_root_delete_allowed(root)
|
||||
|
||||
Returns 1 if system unit files under this root may be deleted.
|
||||
|
||||
=cut
|
||||
sub system_unit_root_delete_allowed
|
||||
{
|
||||
my ($root) = @_;
|
||||
return (local_unit_file_root($root) ||
|
||||
$config{'delete_vendor_units'} eq '1') ? 1 : 0;
|
||||
}
|
||||
|
||||
=head2 unit_visible_on_index(&unit)
|
||||
|
||||
Returns 1 if a unit should be included on index tabs, honoring the option to
|
||||
@@ -3701,7 +3751,7 @@ sub get_unit_root
|
||||
{
|
||||
my ($name, $packaged) = @_;
|
||||
# Common system and vendor unit directories.
|
||||
my $systemd_local_conf = "/etc/systemd/system";
|
||||
my $systemd_local_conf = get_local_unit_root();
|
||||
my $systemd_unit_dir1 = "/usr/lib/systemd/system";
|
||||
my $systemd_unit_dir2 = "/lib/systemd/system";
|
||||
if ($name) {
|
||||
@@ -3713,7 +3763,7 @@ if ($name) {
|
||||
return $p if (-r "$p/$name");
|
||||
}
|
||||
}
|
||||
# Always use /etc/systemd/system for locally created units.
|
||||
# Always use the local administrator directory for locally created units.
|
||||
return $systemd_local_conf if (!$packaged && -d $systemd_local_conf);
|
||||
|
||||
# Debian prefers /lib/systemd/system for packaged units.
|
||||
@@ -3729,6 +3779,16 @@ if (-d $systemd_unit_dir1) {
|
||||
return $systemd_unit_dir2;
|
||||
}
|
||||
|
||||
=head2 get_local_unit_root()
|
||||
|
||||
Returns the local administrator directory for system unit files.
|
||||
|
||||
=cut
|
||||
sub get_local_unit_root
|
||||
{
|
||||
return "/etc/systemd/system";
|
||||
}
|
||||
|
||||
|
||||
=head2 get_unit_pid([name])
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ our (%access, %config, %in, %text, %gconfig, $remote_user);
|
||||
systemd_ereadonly => 'runtime unit file',
|
||||
systemd_ename => 'bad unit name',
|
||||
systemd_egone => 'unit gone',
|
||||
systemd_elocaldelete => 'local unit delete only',
|
||||
systemd_eclash => 'unit clash',
|
||||
systemd_emountwhat => 'missing mount source',
|
||||
systemd_emountname => 'bad mount name',
|
||||
@@ -196,6 +197,27 @@ ok(!unit_file_editable({
|
||||
unitstate => 'generated',
|
||||
}),
|
||||
'generated unit files are read-only');
|
||||
ok(system_unit_file_deletable({
|
||||
name => 'local.service',
|
||||
file => '/etc/systemd/system/local.service',
|
||||
unitstate => 'enabled',
|
||||
}),
|
||||
'local system unit files can be deleted');
|
||||
ok(!system_unit_file_deletable({
|
||||
name => 'vendor.service',
|
||||
file => '/usr/lib/systemd/system/vendor.service',
|
||||
unitstate => 'enabled',
|
||||
}),
|
||||
'packaged system unit files cannot be deleted');
|
||||
{
|
||||
local $config{'delete_vendor_units'} = 1;
|
||||
ok(system_unit_file_deletable({
|
||||
name => 'vendor.service',
|
||||
file => '/usr/lib/systemd/system/vendor.service',
|
||||
unitstate => 'enabled',
|
||||
}),
|
||||
'packaged system unit files can be deleted when configured');
|
||||
}
|
||||
{
|
||||
local $config{'show_runtime_units'} = 0;
|
||||
ok(unit_visible_on_index({
|
||||
@@ -908,9 +930,18 @@ like(get_unit_root(), qr{^/(etc|usr/lib|lib)/systemd/system$},
|
||||
|
||||
{
|
||||
my $root = "$work/system-root";
|
||||
make_path($root);
|
||||
my $vendor_root = "$work/system-vendor-root";
|
||||
make_path($root, $vendor_root);
|
||||
my $reloaded = 0;
|
||||
local *main::get_unit_root = sub { return $root };
|
||||
local *main::get_local_unit_root = sub { return $root };
|
||||
local *main::get_unit_root = sub {
|
||||
my ($name) = @_;
|
||||
return $vendor_root if (defined($name) && $name eq 'vendor.service');
|
||||
return $root;
|
||||
};
|
||||
local *main::get_system_unit_file_root_candidates = sub {
|
||||
return ($root, $vendor_root);
|
||||
};
|
||||
local *main::reload_manager = sub { $reloaded++ };
|
||||
local *main::has_command = sub { return };
|
||||
my ($ok) = create_system_unit(
|
||||
@@ -949,6 +980,20 @@ like(get_unit_root(), qr{^/(etc|usr/lib|lib)/systemd/system$},
|
||||
ok(!$ok, 'delete_system_unit rejects already-missing unit');
|
||||
is($out, $text{'systemd_egone'},
|
||||
'delete_system_unit reports stale missing unit');
|
||||
write_test_file("$vendor_root/vendor.service", "packaged");
|
||||
($ok, $out) = delete_system_unit('vendor.service');
|
||||
ok(!$ok, 'delete_system_unit rejects packaged system unit files');
|
||||
is($out, $text{'systemd_elocaldelete'},
|
||||
'delete_system_unit reports local-only delete policy');
|
||||
ok(-e "$vendor_root/vendor.service",
|
||||
'delete_system_unit leaves packaged system unit file alone');
|
||||
{
|
||||
local $config{'delete_vendor_units'} = 1;
|
||||
($ok, $out) = delete_system_unit('vendor.service');
|
||||
ok($ok, 'delete_system_unit can delete packaged unit files when configured');
|
||||
ok(!-e "$vendor_root/vendor.service",
|
||||
'delete_system_unit removes configured packaged unit file');
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1999,6 +2044,8 @@ like($config_source, qr/^default_create_scope=/m,
|
||||
'module config exposes default create scope');
|
||||
like($config_source, qr/^manual_vendor_units=/m,
|
||||
'module config exposes vendor-file manual editor visibility');
|
||||
like($config_source, qr/^delete_vendor_units=/m,
|
||||
'module config exposes packaged unit delete policy');
|
||||
like($config_source, qr/^default_linger=/m,
|
||||
'module config exposes default linger choice');
|
||||
like($config_source, qr/^show_dropin_inventory=/m,
|
||||
@@ -2236,6 +2283,8 @@ like($edit_source, qr/readonly='readonly'/,
|
||||
'edit page shows runtime-managed unit files as read-only');
|
||||
like($edit_source, qr/unit_file_editable/,
|
||||
'edit page hides save and delete for runtime-managed unit files');
|
||||
like($edit_source, qr/system_unit_file_deletable/,
|
||||
'edit page hides delete for packaged system unit files');
|
||||
like($edit_source, qr/edit_depsnow/,
|
||||
'edit page includes dependency inspect action');
|
||||
like($edit_source, qr/edit_propsnow/,
|
||||
|
||||
Reference in New Issue
Block a user