Add raw config edit ACL and server shortcut

* Note: Gate manual Nginx config editing behind a dedicated ACL and add a per-server quick edit icon for the owning config file.
This commit is contained in:
Ilia Ross
2026-05-19 01:16:19 +02:00
parent 578a41769e
commit 267f05ed73
8 changed files with 110 additions and 9 deletions

View File

@@ -38,6 +38,11 @@ print &ui_table_row($text{'acl_root'},
print &ui_table_row($text{'acl_global'},
&ui_yesno_radio("global", $o->{'global'}));
# Can manually edit configuration files?
print &ui_table_row($text{'acl_manual'},
&ui_yesno_radio("manual",
defined($o->{'manual'}) ? $o->{'manual'} : $o->{'global'}));
# Can edit log files?
print &ui_table_row($text{'acl_logs'},
&ui_yesno_radio("logs", $o->{'logs'}));
@@ -59,6 +64,7 @@ $o->{'edit'} = $in{'edit'};
$o->{'create'} = $in{'create'};
$o->{'root'} = $in{'root'};
$o->{'global'} = $in{'global'};
$o->{'manual'} = $in{'manual'};
$o->{'logs'} = $in{'logs'};
$o->{'user'} = $in{'user'};
$o->{'stop'} = $in{'stop'};

View File

@@ -6,11 +6,11 @@ use warnings;
require './nginx-lib.pl';
&ReadParse();
our (%text, %in, %access);
$access{'global'} || &error($text{'index_eglobal'});
&can_edit_manual_config() || &error($text{'manual_ecannot'});
&ui_print_header(undef, $text{'manual_title'}, "");
my @files = &get_all_config_files();
my @files = &get_manual_config_files();
$in{'file'} ||= $files[0];
&indexof($in{'file'}, @files) >= 0 || &error($text{'manual_efile'});
@@ -38,4 +38,3 @@ print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
&ui_print_footer("", $text{'index_return'});

View File

@@ -28,10 +28,18 @@ if ($in{'id'}) {
my @spages = ( $access{'logs'} ? ( "slogs" ) : ( ),
"sdocs", "ssl", "fcgi", "sssi", "sgzip", "sproxy",
"saccess", "srewrite", );
my @slinks = map { "edit_".$_.".cgi?id=".&urlize($in{'id'}) } @spages;
my @stitles = map { $text{$_."_title"} } @spages;
my @sicons = map { "images/".$_.".gif" } @spages;
if (&can_edit_manual_config() && &can_edit_manual_file($server->{'file'})) {
push(@slinks, "edit_manual.cgi?file=".&urlize($server->{'file'}));
push(@stitles, $text{'manual_server'});
push(@sicons, "images/manual.gif");
}
&icons_table(
[ map { "edit_".$_.".cgi?id=".&urlize($in{'id'}) } @spages ],
[ map { $text{$_."_title"} } @spages ],
[ map { "images/".$_.".gif" } @spages ],
\@slinks,
\@stitles,
\@sicons,
);
# Show table for locations

View File

@@ -48,7 +48,8 @@ print &ui_tabs_start(\@tabs, "mode", $mode, 1);
if ($access{'global'}) {
# Show icons for global config types
print &ui_tabs_start_tab("mode", "global");
my @gpages = ( "net", "mime", "logs", "docs", "ssi", "misc", "manual" );
my @gpages = ( "net", "mime", "logs", "docs", "ssi", "misc",
&can_edit_manual_config() ? ( "manual" ) : ( ) );
&icons_table(
[ map { "edit_".$_.".cgi" } @gpages ],
[ map { $text{$_."_title"} } @gpages ],

View File

@@ -151,12 +151,14 @@ opt_essi_types=$1 is not a valid MIME type
opt_essi_value_length=Maximum parameter length must be a number
manual_title=Edit Configuration Files
manual_server=Edit Configuration File
manual_file=Editing configuration file:
manual_ok=Switch
manual_efile=Selected file is not part of the Nginx configuration!
manual_test=Test new configuration before saving?
manual_elink=Dangling symbolic link!
manual_err=Failed to save configuration file
manual_ecannot=You are not allowed to manually edit configuration files!
server_create=Create a New Server Block
server_edit=Edit Server Block
@@ -419,6 +421,7 @@ acl_create=Can create server blocks?
acl_stop=Can stop and start Nginx?
acl_root=Allowed directories for locations
acl_global=Can edit global settings?
acl_manual=Can manually edit raw configuration files?
acl_logs=Can configure log files?
acl_user=Write password files as user

View File

@@ -570,6 +570,16 @@ if ($parent->{'type'}) {
return &unique(@rv);
}
# get_manual_config_files([&parent])
# Returns all config files this user can manually edit
sub get_manual_config_files
{
my ($parent) = @_;
my @files = &get_all_config_files($parent);
return @files if (!$access{'vhosts'});
return grep { &can_edit_manual_file($_) } @files;
}
# directive_indent(&directive, &parent, &file-lines)
# Returns the exact whitespace prefix to use when writing a directive
sub directive_indent
@@ -2186,6 +2196,22 @@ return 0 if (!$name);
return &indexoflc($name, split(/\s+/, $access{'vhosts'})) >= 0;
}
# can_edit_manual_config()
# Returns 1 if the user can manually edit raw configuration files
sub can_edit_manual_config
{
return defined($access{'manual'}) ? $access{'manual'} : $access{'global'};
}
# can_edit_manual_file(file)
# Returns 1 if the user can manually edit some raw configuration file
sub can_edit_manual_file
{
my ($file) = @_;
return 1 if (!$access{'vhosts'});
return &can_manage_server_file($file);
}
# can_directory(dir)
# Check if some directory is under one of the allowed roots
sub can_directory

View File

@@ -7,9 +7,9 @@ require './nginx-lib.pl';
&ReadParseMime();
our (%text, %in, %access);
&error_setup($text{'manual_err'});
$access{'global'} || &error($text{'index_eglobal'});
&can_edit_manual_config() || &error($text{'manual_ecannot'});
my @files = &get_all_config_files();
my @files = &get_manual_config_files();
&indexof($in{'file'}, @files) >= 0 || &error($text{'manual_efile'});
# Follow links to get the real file

View File

@@ -385,4 +385,62 @@ subtest 'config change apply flag tracks pending changes' => sub {
'apply needed when config change is newer than last apply');
};
subtest 'manual edit ACL is separately configurable' => sub {
no warnings 'once';
{
local %main::access = ( 'global' => 1 );
ok(main::can_edit_manual_config(),
'manual edit defaults to global ACL for existing users');
}
{
local %main::access = ( 'global' => 0 );
ok(!main::can_edit_manual_config(),
'manual edit is denied when global ACL default is denied');
}
{
local %main::access = ( 'global' => 1, 'manual' => 0 );
ok(!main::can_edit_manual_config(),
'manual edit can be disabled for global users');
}
{
local %main::access = ( 'global' => 0, 'manual' => 1 );
ok(main::can_edit_manual_config(),
'manual edit can be explicitly enabled');
}
};
subtest 'manual edit files respect vhost ACL' => sub {
my $single = File::Spec->catfile($available, 'manual-single.conf');
my $shared = File::Spec->catfile($available, 'manual-shared.conf');
write_text($single,
server_conf('single.example', "\troot /srv/single;\n"));
write_text($shared,
server_conf('single.example', "\troot /srv/shared-single;\n").
server_conf('other.example', "\troot /srv/shared-other;\n"));
symlink($single, File::Spec->catfile($enabled, 'manual-single.conf')) ||
die "Failed to symlink manual-single: $!";
symlink($shared, File::Spec->catfile($enabled, 'manual-shared.conf')) ||
die "Failed to symlink manual-shared: $!";
main::flush_config_cache();
{
local %main::access = ( 'manual' => 1,
'vhosts' => 'single.example' );
ok(main::can_edit_manual_file($single),
'restricted user can manually edit their own single-server file');
ok(!main::can_edit_manual_file($shared),
'restricted user cannot manually edit a shared server file');
is_deeply(
[ grep { $_ eq $single || $_ eq $shared }
main::get_manual_config_files() ],
[ $single ],
'manual file list excludes shared files for restricted users');
}
{
local %main::access = ( 'manual' => 1 );
ok(main::can_edit_manual_file($shared),
'unrestricted user can manually edit shared server files');
}
};
done_testing();