--- loncom/interface/domainprefs.pm 2020/09/17 00:35:04 1.372
+++ loncom/interface/domainprefs.pm 2020/12/18 15:23:02 1.373
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Handler to set domain-wide configuration settings
#
-# $Id: domainprefs.pm,v 1.372 2020/09/17 00:35:04 raeburn Exp $
+# $Id: domainprefs.pm,v 1.373 2020/12/18 15:23:02 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -176,6 +176,7 @@ use Locale::Language;
use DateTime::TimeZone;
use DateTime::Locale;
use Time::HiRes qw( sleep );
+use Net::CIDR;
my $registered_cleanup;
my $modified_urls;
@@ -220,7 +221,7 @@ sub handler {
'coursedefaults','usersessions','loadbalancing',
'requestauthor','selfenrollment','inststatus',
'ltitools','ssl','trust','lti','privacy','passwords',
- 'proctoring'],$dom);
+ 'proctoring','wafproxy'],$dom);
my %encconfig =
&Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring'],$dom);
if (ref($domconfig{'ltitools'}) eq 'HASH') {
@@ -259,11 +260,11 @@ sub handler {
}
}
}
- my @prefs_order = ('rolecolors','login','defaults','passwords','quotas','autoenroll',
- 'autoupdate','autocreate','directorysrch','contacts','privacy',
- 'usercreation','selfcreation','usermodification','scantron',
- 'requestcourses','requestauthor','coursecategories',
- 'serverstatuses','helpsettings','coursedefaults',
+ my @prefs_order = ('rolecolors','login','defaults','wafproxy','passwords','quotas',
+ 'autoenroll','autoupdate','autocreate','directorysrch',
+ 'contacts','privacy','usercreation','selfcreation',
+ 'usermodification','scantron','requestcourses','requestauthor'
+ 'coursecategories','serverstatuses','helpsettings','coursedefaults',
'ltitools','proctoring','selfenrollment','usersessions','ssl',
'trust','lti');
my %existing;
@@ -310,6 +311,17 @@ sub handler {
print => \&print_defaults,
modify => \&modify_defaults,
},
+ 'wafproxy' =>
+ { text => 'Web Application Firewall/Reverse Proxy',
+ help => 'Domain_Configuration_WAF_Proxy',
+ header => [{col1 => 'Domain server',
+ col2 => 'Alias for WAF/Reverse Proxy',
+ },
+ {col1 => 'Setting',
+ col2 => 'Value',}],
+ print => \&print_wafproxy,
+ modify => \&modify_wafproxy,
+ },
'passwords' =>
{ text => 'Passwords (Internal authentication)',
help => 'Domain_Configuration_Passwords',
@@ -805,6 +817,8 @@ sub process_changes {
$output = &modify_privacy($dom,%domconfig);
} elsif ($action eq 'passwords') {
$output = &modify_passwords($r,$dom,$confname,$lastactref,%domconfig);
+ } elsif ($action eq 'wafproxy') {
+ $output = &modify_wafproxy($dom,$action,$lastactref,%domconfig);
}
return $output;
}
@@ -882,7 +896,7 @@ sub print_config_box {
($action eq 'usermodification') || ($action eq 'defaults') || ($action eq 'coursedefaults') ||
($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'ssl') ||
($action eq 'directorysrch') || ($action eq 'trust') || ($action eq 'helpsettings') ||
- ($action eq 'contacts') || ($action eq 'privacy')) {
+ ($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'wafproxy')) {
$output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal);
} elsif ($action eq 'passwords') {
$output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal);
@@ -1005,7 +1019,7 @@ sub print_config_box {
$rowtotal ++;
} elsif (($action eq 'usermodification') || ($action eq 'coursedefaults') ||
($action eq 'defaults') || ($action eq 'directorysrch') ||
- ($action eq 'helpsettings')) {
+ ($action eq 'helpsettings') || ($action eq 'wafproxy')) {
$output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal);
} elsif ($action eq 'scantron') {
$output .= $item->{'print'}->($r,'bottom',$dom,$confname,$settings,\$rowtotal);
@@ -7164,6 +7178,134 @@ sub print_passwords {
return $datatable;
}
+sub print_wafproxy {
+ my ($position,$dom,$settings,$rowtotal) = @_;
+ my $css_class;
+ my $itemcount = 0;
+ my $datatable;
+ my %servers = &Apache::lonnet::internet_dom_servers($dom);
+ my (%othercontrol,%otherdoms,%aliases,%values,$setdom);
+ foreach my $server (sort(keys(%servers))) {
+ my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server});
+ my $serverdom;
+ if ($serverhome ne $server) {
+ $serverdom = &Apache::lonnet::host_domain($serverhome);
+ $othercontrol{$server} = $serverdom;
+ } else {
+ $serverdom = &Apache::lonnet::host_domain($server);
+ if ($serverdom ne $dom) {
+ $othercontrol{$server} = $serverdom;
+ } else {
+ $setdom = 1;
+ if (ref($settings) eq 'HASH') {
+ %{$values{$dom}} = ();
+ if (ref($settings->{'alias'}) eq 'HASH') {
+ $aliases{$dom} = $settings->{'alias'};
+ }
+ foreach my $item ('ipheader','trusted','exempt') {
+ $values{$dom}{$item} = $settings->{$item};
+ }
+ }
+ }
+ }
+ }
+ if (keys(%othercontrol)) {
+ %otherdoms = reverse(%othercontrol);
+ foreach my $domain (keys(%otherdoms)) {
+ %{$values{$domain}} = ();
+ my %config = &Apache::lonnet::get_dom('configuration',['wafproxy'],$domain);
+ if (ref($config{$domain}) eq 'HASH') {
+ if (ref($config{$domain}{'wafproxy'}) eq 'HASH') {
+ $aliases{$domain} = $config{$domain}{'wafproxy'}{'alias'};
+ foreach my $item ('exempt','trusted','ipheader') {
+ $values{$domain}{$item} = $config{$domain}{'wafproxy'}{$item};
+ }
+ }
+ }
+ }
+ }
+ if ($position eq 'top') {
+ my %servers = &Apache::lonnet::internet_dom_servers($dom);
+ foreach my $server (sort(keys(%servers))) {
+ $itemcount ++;
+ $css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
+ $datatable .= '
'.
+ ''.&mt('Hostname').': '.
+ &Apache::lonnet::hostname($server).' | '.
+ '';
+ if ($othercontrol{$server}) {
+ my $current;
+ if (ref($aliases{$othercontrol{$server}}) eq 'HASH') {
+ $current = $aliases{$othercontrol{$server}{$server}};
+ }
+ if ($current) {
+ $datatable .= $current;
+ } else {
+ $datatable .= &mt('None in effect');
+ }
+ $datatable .= ' ('.
+ &mt('WAF/Reverse Proxy controlled by domain: [_1]',
+ ''.$othercontrol{$server}.'').'';
+ } else {
+ my $current;
+ if (ref($aliases{$dom}) eq 'HASH') {
+ if ($aliases{$dom}{$server}) {
+ $current = $aliases{$dom}{$server};
+ }
+ }
+ $datatable .= '';
+ }
+ $datatable .= ' |
';
+ }
+ } else {
+ if ($setdom) {
+ $itemcount ++;
+ $css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
+ $datatable .= ''.
+ ''.&mt('Domain: [_1]',''.$dom.'').'
'.
+ &mt('Format for comma separated IP blocks').': '.
+ &mt('A.B.C.D/N or A.B.C.D - E.F.G.H').' | '.
+ ' |
';
+ }
+ if (keys(%otherdoms)) {
+ foreach my $domain (sort(keys(%otherdoms))) {
+ $itemcount ++;
+ $css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
+ $datatable .= ''.
+ ''.&mt('Domain: [_1]',$domain).
+ '';
+ foreach my $item ('ipheader','trusted','exempt') {
+ my $showval = &mt('None');
+ if ($values{$domain}{$item}) {
+ $showval = $values{$domain}{$item};
+ }
+ $datatable .= ''.
+ ''.$lt{$item}.': '.$showval.' | ';
+ }
+ $datatable .= ' |
';
+ }
+ }
+ }
+ $$rowtotal += $itemcount;
+ return $datatable;
+}
+
+sub wafproxy_titles {
+ return &Apache::lonlocal::texthash(
+ exempt => 'Exempt IP range(s)',
+ trusted => 'Trusted IP range(s)',
+ ipheader => 'Custom request header',
+ );
+}
+
sub print_usersessions {
my ($position,$dom,$settings,$rowtotal) = @_;
my ($css_class,$datatable,$itemcount,%checked,%choices);
@@ -19426,6 +19568,215 @@ sub modify_selfenrollment {
return $resulttext;
}
+sub modify_wafproxy {
+ my ($dom,$action,$lastactref,%domconfig) = @_;
+ my %servers = &Apache::lonnet::internet_dom_servers($dom);
+ my (%othercontrol,%canset,%values,%curralias,%currvalue,@warnings,%wafproxy,
+ %changes,%expirecache);
+ foreach my $server (sort(keys(%servers))) {
+ my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server});
+ if ($serverhome eq $server) {
+ my $serverdom = &Apache::lonnet::host_domain($server);
+ if ($serverdom eq $dom) {
+ $canset{$server} = 1;
+ if (ref($domconfig{'wafproxy'}) eq 'HASH') {
+ %{$values{$dom}} = ();
+ if (ref($domconfig{'wafproxy'}{'alias'}) eq 'HASH') {
+ %curralias = %{$domconfig{'wafproxy'}{'alias'}};
+ }
+ foreach my $item ('ipheader','trusted','exempt') {
+ $currvalue{$item} = $domconfig{'wafproxy'}{$item};
+ }
+ }
+ }
+ }
+ }
+ my $output;
+ if (keys(%canset)) {
+ %{$wafproxy{'alias'}} = ();
+ foreach my $key (sort(keys(%canset))) {
+ $wafproxy{'alias'}{$key} = $env{'form.wafproxy_alias_'.$key};
+ $wafproxy{'alias'}{$key} =~ s/^\s+|\s+$//g;
+ if ($wafproxy{'alias'}{$key} ne $curralias{$key}) {
+ $changes{'alias'} = 1;
+ }
+ if ($wafproxy{'alias'}{$key} eq '') {
+ if ($curralias{$key}) {
+ $expirecache{$key} = 1;
+ }
+ delete($wafproxy{'alias'}{$key});
+ }
+ }
+ unless (keys(%{$wafproxy{'alias'}})) {
+ delete($wafproxy{'alias'});
+ }
+ # Localization for values in %warn occus in &mt() calls separately.
+ my %warn = (
+ trusted => 'trusted IP range(s)',
+ exempt => 'exempt IP range(s)',
+ );
+ foreach my $item ('ipheader','trusted','exempt') {
+ my $possible = $env{'form.wafproxy_'.$item};
+ $possible =~ s/^\s+|\s+$//g;
+ if ($possible ne '') {
+ if ($item eq 'ipheader') {
+ $wafproxy{$item} = $possible;
+ } else {
+ my (@ok,$count);
+ $possible =~ s/[\r\n]+/\s/g;
+ $possible =~ s/\s*-\s*/-/g;
+ $possible =~ s/\s+/,/g;
+ $count = 0;
+ if ($possible) {
+ foreach my $poss (split(/\,/,$possible)) {
+ $count ++;
+ if (&validate_ip_pattern($poss)) {
+ push(@ok,$poss);
+ }
+ }
+ if (@ok) {
+ $wafproxy{$item} = join(',',@ok);
+ }
+ my $diff = $count - scalar(@ok);
+ if ($diff) {
+ push(@warnings,''.
+ &mt('[quant,_1,IP] invalid and excluded from saved value for [_2]',
+ $diff,$warn{$item}).
+ '');
+ }
+ if ($wafproxy{$item} ne $currvalue{$item}) {
+ $changes{$item} = 1;
+ }
+ }
+ }
+ } else {
+ if ($currvalue{$item}) {
+ $changes{$item} = 1;
+ }
+ }
+ }
+ }
+ if (keys(%changes)) {
+ my %defaultshash = (
+ wafproxy => \%wafproxy,
+ );
+ my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash,
+ $dom);
+ if ($putresult eq 'ok') {
+ my $cachetime = 24*60*60;
+ my (%domdefaults,$updatedomdefs);
+ foreach my $item ('ipheader','trusted','exempt') {
+ if ($changes{$item}) {
+ unless ($updatedomdefs) {
+ %domdefaults = &Apache::lonnet::get_domain_defaults($dom);
+ $updatedomdefs = 1;
+ }
+ if ($wafproxy{$item}) {
+ $domdefaults{'waf_'.$item} = $wafproxy{$item};
+ } elsif (exists($domdefaults{'waf_'.$item})) {
+ delete($domdefaults{'waf_'.$item});
+ }
+ }
+ }
+ if ($updatedomdefs) {
+ &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime);
+ if (ref($lastactref) eq 'HASH') {
+ $lastactref->{'domdefaults'} = 1;
+ }
+ }
+ if ((exists($wafproxy{'alias'})) || (keys(%expirecache))) {
+ my %updates = %expirecache;
+ foreach my $key (keys(%expirecache)) {
+ &Apache::lonnet::devalidate_cache_new('proxyalias',$key);
+ }
+ if (ref($wafproxy{'alias'}) eq 'HASH') {
+ my $cachetime = 24*60*60;
+ foreach my $key (keys(%{$wafproxy{'alias'}})) {
+ $updates{$key} = 1;
+ &Apache::lonnet::do_cache_new('proxyalias',$key,$wafproxy{'alias'}{$key},
+ $cachetime);
+ }
+ }
+ if (ref($lastactref) eq 'HASH') {
+ $lastactref->{'proxyalias'} = \%updates;
+ }
+ }
+ $output = &mt('Changes were made to Web Application Firewall/Reverse Proxy').'';
+ foreach my $item ('alias','ipheader','trusted','exempt') {
+ if ($changes{$item}) {
+ if ($item eq 'alias') {
+ my $numaliased = 0;
+ if (ref($wafproxy{'alias'}) eq 'HASH') {
+ my $shown;
+ if (keys(%{$wafproxy{'alias'}})) {
+ foreach my $server (sort(keys(%{$wafproxy{'alias'}}))) {
+ $shown .= '- '.&mt('[_1] aliased by [_2]',
+ &Apache::lonnet::hostname($server),
+ $wafproxy{'alias'}{$server}).'
';
+ $numaliased ++;
+ }
+ if ($numaliased) {
+ $output .= '- '.&mt('Aliases for hostnames set to: [_1]',
+ '').'
';
+ }
+ }
+ }
+ unless ($numaliased) {
+ $output .= '- '.&mt('Aliases deleted for hostnames').'
';
+ }
+ } else {
+ if ($item eq 'ipheader') {
+ if ($wafproxy{$item}) {
+ $output .= '- '.&mt('Custom request header set to [_1]',
+ $wafproxy{$item}).'
';
+ } else {
+ $output .= '- '.&mt('Custom request header deleted').'
';
+ }
+ } elsif ($item eq 'trusted') {
+ if ($wafproxy{$item}) {
+ $output .= '- '.&mt('Trusted IP range(s) set to [_1]',
+ $wafproxy{$item}).'
';
+ } else {
+ $output .= '- '.&mt('Trusted IP range(s) deleted').'
';
+ }
+ } elsif ($item eq 'exempt') {
+ if ($wafproxy{$item}) {
+ $output .= '- '.&mt('Exempt IP range(s) set to [_1]',
+ $wafproxy{$item}).'
';
+ } else {
+ $output .= '- '.&mt('Exempt IP range(s) deleted').'
';
+ }
+ }
+ }
+ }
+ }
+ } else {
+ $output = ''.
+ &mt('An error occurred: [_1]',$putresult).'';
+ }
+ } elsif (keys(%canset)) {
+ $output = &mt('No changes made to Web Application Firewall/Reverse Proxy settings');
+ }
+ if (@warnings) {
+ $output .= '
'.&mt('Warnings:').''.
+ join("\n",@warnings).'
';
+ }
+ return $output;
+}
+
+sub validate_ip_pattern {
+ my ($pattern) = @_;
+ if ($pattern =~ /^([^-]+)\-([^-]+)$/) {
+ my ($start,$end) = ($1,$2);
+ if ((&Net::CIDR::cidrvalidate($start)) && (&Net::CIDR::cidrvalidate($end))) {
+ return 1;
+ }
+ } elsif (&Net::CIDR::cidrvalidate($pattern)) {
+ return 1;
+ }
+ return
+}
+
sub modify_usersessions {
my ($dom,$lastactref,%domconfig) = @_;
my @hostingtypes = ('version','excludedomain','includedomain');
@@ -21140,14 +21491,22 @@ sub devalidate_remote_domconfs {
my %thismachine;
map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids();
my @posscached = ('domainconfig','domdefaults','ltitools','usersessions',
- 'directorysrch','passwdconf','cats');
+ 'directorysrch','passwdconf','cats','proxyalias');
if (keys(%servers)) {
foreach my $server (keys(%servers)) {
next if ($thismachine{$server});
my @cached;
foreach my $name (@posscached) {
if ($cachekeys->{$name}) {
- push(@cached,&escape($name).':'.&escape($dom));
+ if ($name eq 'proxyalias') {
+ if (ref($cachekeys->{$name}) eq 'HASH') {
+ foreach my $key (keys(%{$cachekeys->{$name}})) {
+ push(@cached,&escape($name).':'.&escape($key));
+ }
+ }
+ } else {
+ push(@cached,&escape($name).':'.&escape($dom));
+ }
}
}
if (@cached) {