06228918d6
Signed-off-by: Digimer <digimer@alteeve.ca>
308 lines
12 KiB
Perl
Executable File
308 lines
12 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
#
|
|
# This keeps an eye on the network configuration and ensures the firewall is configured appropriately. What
|
|
# exactly that means depends on why kind of machine the local host is.
|
|
#
|
|
#
|
|
# Exit codes;
|
|
# 0 = Normal exit.
|
|
# 1 = Failed to unlink an unneeded file.
|
|
# 2 = Failed to write or update a file.
|
|
#
|
|
# TODO:
|
|
# - Add support for enabling/disabling MASQ'ing the BCN
|
|
#
|
|
# # Allow routing/masq'ing through the IFN1 (provide net access to the BCN)
|
|
# firewall-cmd --zone=IFN1 --add-masquerade
|
|
# # Check
|
|
# firewall-cmd --zone=IFN1 --query-masquerade
|
|
# #[yes|no]
|
|
# # Disable
|
|
# # NOTE: Doesn't break existing connections
|
|
# firewall-cmd --zone=IFN1 --remove-masquerade
|
|
#
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Anvil::Tools;
|
|
use Data::Dumper;
|
|
use Text::Diff;
|
|
|
|
|
|
# Disable buffering
|
|
$| = 1;
|
|
|
|
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
|
|
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
|
|
if (($running_directory =~ /^\./) && ($ENV{PWD}))
|
|
{
|
|
$running_directory =~ s/^\./$ENV{PWD}/;
|
|
}
|
|
|
|
my $anvil = Anvil::Tools->new();
|
|
|
|
# If the user has disabled auto-management of the firewall, exit.
|
|
if (not $anvil->data->{sys}{manage}{firewall})
|
|
{
|
|
# Do nothing.
|
|
$anvil->nice_exit({exit_code => 0});
|
|
}
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 3, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }});
|
|
|
|
# Read switches
|
|
$anvil->data->{switches}{'y'} = "";
|
|
$anvil->Get->switches;
|
|
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 2, level => 3, key => "message_0134"});
|
|
check_initial_setup($anvil);
|
|
|
|
# We're done
|
|
$anvil->nice_exit({exit_code => 0});
|
|
|
|
|
|
#############################################################################################################
|
|
# Private functions. #
|
|
#############################################################################################################
|
|
|
|
sub check_initial_setup
|
|
{
|
|
my ($anvil) = @_;
|
|
|
|
# See what we've found... We'll look at what 'check_firewall' finds later to know if any unused zones
|
|
# need to be removed.
|
|
my $needed_zones = [];
|
|
|
|
# This will get set if we need to restart the firewalld daemon.
|
|
$anvil->data->{firewall}{reload} = 0;
|
|
|
|
# Get a list of networks.
|
|
$anvil->System->get_ips({debug => 3});
|
|
|
|
# Get the list of existing zones from iptables/firewalld.
|
|
$anvil->System->check_firewall({debug => 3});
|
|
my $internet_zone = "";
|
|
foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{sys}{network}{interface}})
|
|
{
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { interface => $interface }});
|
|
if ($interface =~ /^((bcn|ifn|sn)\d+)_/)
|
|
{
|
|
# We'll use the start of the string (network type) as the zone, though it should
|
|
# always be overridden by the ZONE="" variable in each interface's config.
|
|
my $zone = $1;
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { zone => $zone }});
|
|
if ((exists $anvil->data->{sys}{network}{interface}{$interface}{variable}{ZONE}) && ($anvil->data->{sys}{network}{interface}{$interface}{variable}{ZONE}))
|
|
{
|
|
$zone = $anvil->data->{sys}{network}{interface}{$interface}{variable}{ZONE};
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { zone => $zone }});
|
|
}
|
|
push @{$needed_zones}, $zone;
|
|
|
|
$anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{ip} = $anvil->data->{sys}{network}{interface}{$interface}{ip};
|
|
$anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{subnet} = $anvil->data->{sys}{network}{interface}{$interface}{subnet};
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
|
|
"firewall::zone::${zone}::interface::${interface}::ip" => $anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{ip},
|
|
"firewall::zone::${zone}::interface::${interface}::subnet" => $anvil->data->{firewall}{zone}{$zone}{interface}{$interface}{subnet},
|
|
}});
|
|
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
|
|
"sys::network::interface::${interface}::default_gateway" => $anvil->data->{sys}{network}{interface}{$interface}{default_gateway},
|
|
}});
|
|
if ($anvil->data->{sys}{network}{interface}{$interface}{default_gateway})
|
|
{
|
|
$internet_zone = $zone;
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { internet_zone => $internet_zone }});
|
|
}
|
|
}
|
|
}
|
|
|
|
# Process the list of existing zones from iptables/firewalld.
|
|
foreach my $zone (sort {$a cmp $b} keys %{$anvil->data->{firewall}{zone}})
|
|
{
|
|
my $file = exists $anvil->data->{firewall}{zone}{$zone}{file} ? $anvil->data->{firewall}{zone}{$zone}{file} : $anvil->data->{path}{directories}{firewalld_zones}."/".$zone.".xml";
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
|
|
"s1:zone" => $zone,
|
|
"s2:file" => $file,
|
|
}});
|
|
|
|
### NOTE: This is probably overkill.
|
|
# Is this a zone I want/need?
|
|
my $wanted = 0;
|
|
foreach my $needed_zone (sort {$a cmp $b} @{$needed_zones})
|
|
{
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
|
|
"s1:zone" => $zone,
|
|
"s2:needed_zone" => $needed_zone,
|
|
}});
|
|
if ($needed_zone eq $zone)
|
|
{
|
|
$wanted = 1;
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { wanted => $wanted }});
|
|
last;
|
|
}
|
|
}
|
|
|
|
# Skip if this is a zone I don't care about.
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { wanted => $wanted }});
|
|
next if not $wanted;
|
|
|
|
# Create or update the zone file, if needed.
|
|
my $template = "";
|
|
my $description = "";
|
|
if ($zone =~ /bcn(\d+)/i)
|
|
{
|
|
my $number = $1;
|
|
$template = "bcn_zone";
|
|
$description = $anvil->Words->string({key => "message_0131", variables => { number => $number }});
|
|
}
|
|
elsif ($zone =~ /sn(\d+)/i)
|
|
{
|
|
my $number = $1;
|
|
$template = "sn_zone";
|
|
$description = $anvil->Words->string({key => "message_0132", variables => { number => $number }});
|
|
}
|
|
elsif ($zone =~ /ifn(\d+)/i)
|
|
{
|
|
my $number = $1;
|
|
$template = "ifn_zone";
|
|
$description = $anvil->Words->string({key => "message_0133", variables => { number => $number }});
|
|
}
|
|
else
|
|
{
|
|
# This should never be hit, but it's a fail-safe in we're in a zone we don't manage.
|
|
next;
|
|
}
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
|
|
"s1:template" => $template,
|
|
"s2:description" => $description,
|
|
}});
|
|
|
|
my $new_zone_body = $anvil->Template->get({file => "firewall.txt", show_name => 0, name => $template, variables => {
|
|
zone => $zone,
|
|
description => $description,
|
|
}});
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { new_zone_body => $new_zone_body }});
|
|
|
|
# This is another fail safe, don't edit unless we have a new file body.
|
|
if (not $new_zone_body)
|
|
{
|
|
next;
|
|
}
|
|
|
|
# If there isn't a body, see if the file exists. If it doesn't, create it. If it does, read it.
|
|
my $update_file = 0;
|
|
my $old_zone_body = exists $anvil->data->{firewall}{zone}{$zone}{body} ? $anvil->data->{firewall}{zone}{$zone}{body} : "";
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { old_zone_body => $old_zone_body }});
|
|
if (-e $file)
|
|
{
|
|
# Has it changed?
|
|
my $diff = diff \$old_zone_body, \$new_zone_body, { STYLE => 'Unified' };
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { diff => $diff }});
|
|
if ($diff)
|
|
{
|
|
# Update it
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0136", variables => { zone => $zone, file => $file }});
|
|
$update_file = 1;
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { update_file => $update_file }});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Create it
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0137", variables => { zone => $zone, file => $file }});
|
|
$update_file = 1;
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { update_file => $update_file }});
|
|
}
|
|
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { update_file => $update_file }});
|
|
if ($update_file)
|
|
{
|
|
my $error = $anvil->Storage->write_file({
|
|
file => $file,
|
|
body => $new_zone_body,
|
|
group => "root",
|
|
user => "root",
|
|
mode => "0644",
|
|
overwrite => 1,
|
|
});
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { error => $error }});
|
|
|
|
if ($error)
|
|
{
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0043", variables => { file => $file }});
|
|
$anvil->nice_exit({exit_code => 2});
|
|
}
|
|
|
|
reload_firewall($anvil);
|
|
}
|
|
|
|
# Make sure the appropriate interfaces are in this zone.
|
|
foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{firewall}{zone}{$zone}{interface}})
|
|
{
|
|
my $in_zone = exists $anvil->data->{firewall}{interface}{$interface}{zone} ? $anvil->data->{firewall}{interface}{$interface}{zone} : "";
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
|
|
"s1:interface" => $interface,
|
|
"s2:in_zone" => $in_zone,
|
|
"s3:zone" => $zone,
|
|
}});
|
|
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { in_zone => $in_zone, zone => $zone }});
|
|
if ((not $in_zone) or ($zone ne $in_zone))
|
|
{
|
|
# Add it
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0138", variables => {
|
|
interface => $interface,
|
|
zone => $zone,
|
|
}});
|
|
|
|
my $output = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{'firewall-cmd'}." --zone=".$zone." --change-interface=".$interface});
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }});
|
|
|
|
reload_firewall($anvil);
|
|
}
|
|
|
|
# Delete it so we know this one has been processed.
|
|
delete $anvil->data->{firewall}{interface}{$interface};
|
|
}
|
|
}
|
|
|
|
# Do we need to update the default zone?
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
|
|
internet_zone => $internet_zone,
|
|
"firewall::default_zone" => $anvil->data->{firewall}{default_zone},
|
|
}});
|
|
if (($internet_zone) && ($anvil->data->{firewall}{default_zone}) && ($anvil->data->{firewall}{default_zone} ne $internet_zone))
|
|
{
|
|
# Yup
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 3, key => "message_0141", variables => { zone => $internet_zone }});
|
|
my $output = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{'firewall-cmd'}." --set-default-zone=".$internet_zone});
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }});
|
|
}
|
|
|
|
# NOTE: We may want to do machine-specific stuff down the road.
|
|
#my $type = $anvil->System->get_host_type();
|
|
#$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { type => $type }});
|
|
|
|
# Restart, if needed.
|
|
if ($anvil->data->{firewall}{reload})
|
|
{
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 3, key => "message_0140"});
|
|
$anvil->System->restart_daemon({debug => 2, daemon => "firewalld"});
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
# Reload the firewall as we change zones.
|
|
sub reload_firewall
|
|
{
|
|
my ($anvil) = @_;
|
|
|
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "message_0139"});
|
|
my $output = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{'firewall-cmd'}." --complete-reload"});
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }});
|
|
|
|
$anvil->data->{firewall}{reload} = 1;
|
|
|
|
return(0);
|
|
}
|