diff --git a/nginx/acl_security.pl b/nginx/acl_security.pl index 242409cda..dacdc9eec 100755 --- a/nginx/acl_security.pl +++ b/nginx/acl_security.pl @@ -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'}; diff --git a/nginx/edit_manual.cgi b/nginx/edit_manual.cgi index 71cbe0678..0bc23dcc6 100755 --- a/nginx/edit_manual.cgi +++ b/nginx/edit_manual.cgi @@ -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'}); - diff --git a/nginx/edit_server.cgi b/nginx/edit_server.cgi index 8df1bb388..8522f7e2b 100755 --- a/nginx/edit_server.cgi +++ b/nginx/edit_server.cgi @@ -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 diff --git a/nginx/index.cgi b/nginx/index.cgi index 8de35fade..e28fcee7b 100755 --- a/nginx/index.cgi +++ b/nginx/index.cgi @@ -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 ], diff --git a/nginx/lang/en b/nginx/lang/en index 16f73813b..e66dd633b 100644 --- a/nginx/lang/en +++ b/nginx/lang/en @@ -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 diff --git a/nginx/nginx-lib.pl b/nginx/nginx-lib.pl index 0a26d7600..95ef21e1b 100644 --- a/nginx/nginx-lib.pl +++ b/nginx/nginx-lib.pl @@ -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 diff --git a/nginx/save_manual.cgi b/nginx/save_manual.cgi index 1b2f469a2..ffedfcd97 100755 --- a/nginx/save_manual.cgi +++ b/nginx/save_manual.cgi @@ -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 diff --git a/nginx/t/server-files.t b/nginx/t/server-files.t index ea2de046d..13d49b917 100644 --- a/nginx/t/server-files.t +++ b/nginx/t/server-files.t @@ -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();