Major cleanup of DYNAMIC_BLACKLIST code

1) Avoid having to parse the setting in the Zones, Misc and rules modules
2) Apply ipset match rule after dealing with exclusions rather than before
3) Correct handling of src-dst

Signed-off-by: Tom Eastep <teastep@shorewall.net>
This commit is contained in:
Tom Eastep 2024-03-05 14:45:41 -08:00
parent dfd40ee208
commit a9c2ee3a76
5 changed files with 121 additions and 83 deletions

View File

@ -8919,10 +8919,10 @@ sub ensure_ipsets( @ ) {
my $set;
my $counters = have_capability( 'IPSET_MATCH_COUNTERS' ) ? ' counters' : '';
if ( $_[0] eq $globals{DBL_IPSET} ) {
if ( $_[0] eq $globals{DBL_IPSET_NAME} ) {
shift;
emit( qq( if ! qt \$IPSET list $globals{DBL_IPSET}; then));
emit( qq( if ! qt \$IPSET list $globals{DBL_IPSET_NAME}; then));
push_indent;
@ -8930,12 +8930,12 @@ sub ensure_ipsets( @ ) {
emit( q( #),
q( # Set the timeout for the dynamic blacklisting ipset),
q( #),
qq( \$IPSET -exist create $globals{DBL_IPSET} hash:net family inet timeout 0${counters}) );
qq( \$IPSET -exist create $globals{DBL_IPSET_NAME} hash:net family inet timeout 0${counters}) );
} else {
emit( q( #),
q( # Set the timeout for the dynamic blacklisting ipset),
q( #),
qq( \$IPSET -exist create $globals{DBL_IPSET} hash:net family inet6 timeout 0${counters}) );
qq( \$IPSET -exist create $globals{DBL_IPSET_NAME} hash:net family inet6 timeout 0${counters}) );
}
pop_indent;
@ -9158,7 +9158,7 @@ sub create_load_ipsets() {
if ( $config{SAVE_IPSETS} || @{$globals{SAVED_IPSETS}} ) {
emit( ' if [ -f ${VARDIR}/ipsets.save ]; then' );
if ( my $set = $globals{DBL_IPSET} ) {
if ( my $set = $globals{DBL_IPSET_NAME} ) {
emit( ' #',
' # Update the dynamic blacklisting ipset timeout value',
' #',

View File

@ -313,6 +313,16 @@ our %EXPORT_TAGS = ( internal => [ qw( create_temp_script
OPTIMIZE_POLICY_MASK
OPTIMIZE_RULESET_MASK
OPTIMIZE_ALL
DBL_NONE
DBL_SRC
DBL_DST
DBL_SRC_DST
DBL_IPSET
DBL_CLASSIC
DBL_DISCONNECT
DBL_LOG
DBL_NOUPDATE
) , ] ,
protocols => [ qw (
TCP
@ -822,6 +832,19 @@ our %filecache;
our $compiletime;
our $test;
#
# Dynamic blacklisting values
#
use constant { DBL_NONE => 0,
DBL_SRC => 1,
DBL_DST => 2,
DBL_SRC_DST => 3,
DBL_CLASSIC => 4,
DBL_IPSET => 8,
DBL_DISCONNECT => 16,
DBL_LOG => 32,
DBL_NOUPDATE => 64
};
sub process_shorewallrc($$);
sub add_variables( \% );
@ -894,8 +917,11 @@ sub initialize($;$$$$) {
RPFILTER_LOG_TAG => '',
INVALID_LOG_TAG => '',
UNTRACKED_LOG_TAG => '',
DBL_IPSET => '',
DBL => DBL_NONE,
DBL_IPSET_NAME => '',
DBL_TIMEOUT => 0,
DBL_TAG => '',
DBL_LEVEL => '',
POSTROUTING => 'POSTROUTING',
);
#
@ -5322,7 +5348,7 @@ sub determine_capabilities() {
fatal_error 'Your kernel/iptables do not include state match support. No version of Shorewall will run on this system'
unless
qt1( "$iptables $iptablesw -A $sillyname -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT") ||
qt1( "$iptables $iptablesw -A $sillyname -m state --state ESTABLISHED,RELATED -j ACCEPT");;
qt1( "$iptables $iptablesw -A $sillyname -m state --state ESTABLISHED,RELATED -j ACCEPT");
$globals{KLUDGEFREE} = $capabilities{KLUDGEFREE} = detect_capability 'KLUDGEFREE';
}
@ -5742,7 +5768,7 @@ sub process_shorewall_conf( $$ ) {
# Now update the config file if asked
#
if ( $update ) {
update_config_file( $annotate );
update_config_file( $annotate );
#
# Config file update requires that the option values not have
# Shell variables expanded. We do that now.
@ -6712,26 +6738,38 @@ sub get_configuration( $$$ ) {
if ( supplied( $val = $config{DYNAMIC_BLACKLIST} ) ) {
if ( $val =~ /^ipset/ ) {
my %simple_options = ( 'src-dst' => 1, 'disconnect' => 1, 'log' => 1, 'noupdate' => 1, );
my $setting = DBL_IPSET;
$setting |= DBL_SRC;
$setting |= DBL_CLASSIC unless ( $val =~ /^ipset-only/ );
$setting |= DBL_DST if ( $val =~ /,(src-)?dst[,:]/ );
my %simple_options = ( 'src-dst' => DBL_SRC_DST,
'disconnect' => DBL_DISCONNECT,
'log' => DBL_LOG,
'noupdate' => DBL_NOUPDATE,
);
my ( $key, $set, $level, $tag, $rest ) = split( ':', $val , 5 );
( $key , my @options ) = split_list( $key, 'option' );
my $options = '';
for ( @options ) {
if ( $simple_options{$_} ) {
$options = join( ',' , $options, $_ );
} elsif ( $_ =~ s/^timeout=(\d+)$// ) {
my $tmp;
if ( $_ =~ s/^timeout=(\d+)$// ) {
$globals{DBL_TIMEOUT} = $1;
} elsif ( $tmp = $simple_options {$_} ) {
$setting |= $tmp;
} else {
fatal_error "Invalid ipset option ($_)";
if ( $_ =~ /^timeout=(.+)/ ) {
fatal_error( "Invalid Timeout ($1)" )
} else {
fatal_error "Invalid ipset option ($_)";
}
}
}
$globals{DBL_OPTIONS} = $options;
fatal_error "Invalid DYNAMIC_BLACKLIST setting ( $val )" if $key !~ /^ipset(?:-only)?$/ || defined $rest;
if ( supplied( $set ) ) {
@ -6740,7 +6778,7 @@ sub get_configuration( $$$ ) {
$set = 'SW_DBL' . $family;
}
add_ipset( $globals{DBL_IPSET} = $set );
add_ipset( $globals{DBL_IPSET_NAME} = $set );
$level = validate_level( $level );
@ -6753,11 +6791,16 @@ sub get_configuration( $$$ ) {
$variables{SW_DBL_IPSET} = $set;
$variables{SW_DBL_TIMEOUT} = $globals{DBL_TIMEOUT};
$globals{DBL} = $setting;
$globals{DBL_LEVEL} = $level;
$globals{DBL_TAG} = $tag;
} else {
default_yes_no( 'DYNAMIC_BLACKLIST', 'Yes' );
$globals{DBL} = $config{DYNAMIC_BLACKLIST} ? DBL_CLASSIC : DBL_NONE;
}
} else {
default_yes_no( 'DYNAMIC_BLACKLIST', 'Yes' );
$globals{DBL} = $config{DYNAMIC_BLACKLIST} ? DBL_CLASSIC : DBL_NONE;
}
add_variables( %variables );

View File

@ -710,10 +710,10 @@ sub create_docker_rules() {
sub setup_mss();
sub add_ipset_dbl_jump( $$$@) {
sub add_ipset_dbl_ijump( $$$@) {
my ( $chainref, $target, $ipset_dir ) = ( shift, shift, shift );
add_ijump_extended( $chainref, j => $target, $origin{DYNAMIC_BLACKLIST}, @_, "--match-set $ipset_dir" );
add_ijump_extended( $chainref, j => $target, $origin{DYNAMIC_BLACKLIST}, @_, "set --match-set" => $ipset_dir );
}
#
@ -739,9 +739,9 @@ sub add_common_rules ( $ ) {
my $dbl_ipset;
my $dbl_level;
my $dbl_tag;
my $dbl_timeout;
my $dbl_src_target;
my $dbl_dst_target;
my $dbl_options;
if ( $config{REJECT_ACTION} ) {
process_reject_action;
@ -791,13 +791,8 @@ sub add_common_rules ( $ ) {
#
create_docker_rules if $config{DOCKER};
if ( my $val = $config{DYNAMIC_BLACKLIST} ) {
#
# $config{DYNAMIC_BLACKLIST} was normalized in Shorewall:Config:get_configuration - it is probably not what is specified in the shorewall[6].conf
#
( $dbl_type, $dbl_ipset, $dbl_level, $dbl_tag ) = split( ':', $val );
unless ( $dbl_type =~ /^ipset-only/ ) {
if ( my $dbl = $globals{DBL} ) {
if ( $dbl & DBL_CLASSIC ) {
#
# Classic chain-based backlisting
#
@ -807,13 +802,14 @@ sub add_common_rules ( $ ) {
add_commands( $dynamicref, '[ -f ${VARDIR}/.dynamic ] && cat ${VARDIR}/.dynamic >&3' );
}
if ( $dbl_ipset ) {
if ( $dbl & DBL_IPSET ) {
#
# ipset-based blacklisting - not mutually exclusive with the classic type
#
if ( $val = $globals{DBL_TIMEOUT} ) {
$dbl_options = $globals{DBL_OPTIONS};
$dbl_src_target = $dbl_options =~ /src-dst/ ? 'dbl_src' : 'dbl_log';
( $dbl_ipset, $dbl_level, $dbl_tag , $dbl_timeout) = ( $globals{DBL_IPSET_NAME}, $globals{DBL_LEVEL}, $globals{DBL_TAG}, $globals{DBL_TIMEOUT} );
if ( $dbl_timeout ) {
$dbl_src_target = ( ($dbl & DBL_SRC_DST) == DBL_SRC_DST ) ? 'dbl_src' : 'dbl_log';
my $chainref = new_standard_chain( $dbl_src_target );
@ -826,7 +822,7 @@ sub add_common_rules ( $ ) {
'add',
'',
$origin{DYNAMIC_BLACKLIST} ) if $dbl_level;
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset src --exist --timeout $val", $origin{DYNAMIC_BLACKLIST} ) unless $dbl_options =~ /noupdate/;
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset src --exist --timeout $globals{DBL_TIMEOUT}", $origin{DYNAMIC_BLACKLIST} ) unless $dbl & DBL_NOUPDATE;;
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
if ( $dbl_src_target eq 'dbl_src' ) {
@ -841,7 +837,7 @@ sub add_common_rules ( $ ) {
'add',
'',
$origin{DYNAMIC_BLACKLIST} ) if $dbl_level;
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset dst --exist --timeout $val", $origin{DYNAMIC_BLACKLIST} );
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset dst --exist --timeout $globals{DBL_TIMEOUT}", $origin{DYNAMIC_BLACKLIST} );
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
} else {
$dbl_dst_target = $dbl_src_target;
@ -973,10 +969,22 @@ sub add_common_rules ( $ ) {
#
# Dynamic Blacklisting
#
my ( $src_target, $dst_target, $classic_target ) = ( $dbl_src_target, $dbl_dst_target , $dynamicref->{name} );
my ( $input_option_chainref,
$forward_option_chainref,
$output_option_chainref,
$classic_target_chain,
)
=
( $filter_table->{input_option_chain($interface)},
$filter_table->{forward_option_chain($interface)},
$filter_table->{output_option_chain($interface)},
$dynamicref,
);
if ( ( my $setting = get_interface_option( $interface, 'dbl' ) ) != DBL_NONE ) {
my @matches = @state;
my ( @src_exclude, @dst_exclude, @classic_exclude );
my @nodbl = @{$interfaceref->{nodbl}};
@ -990,27 +998,32 @@ sub add_common_rules ( $ ) {
# We need to create an intermediate chain
#
if ( $dbl_ipset ) {
$chainref = new_standard_chain( $src_target = nodbl_src_chain( $interface ));
$chainref = new_standard_chain( nodbl_src_chain( $interface ));
for (@nodbl) {
add_ijump( $chainref, j => 'RETURN', s => $_ );
}
add_ijump( $chainref, j => $dbl_src_target );
add_ijump( $input_option_chainref, j => $chainref->{name} );
add_ijump( $forward_option_chainref, j => $chainref->{name} );
$input_option_chainref = $forward_option_chainref = $chainref;
if ( $dbl_src_target ne $dbl_dst_target ) {
$chainref = new_standard_chain( $dst_target = nodbl_dst_chain( $interface ));
$chainref = new_standard_chain( nodbl_dst_chain( $interface ));
for ( @nodbl ){
add_ijump( $chainref, j => 'RETURN', -d => $_ );
}
add_ijump( $chainref, j => $dbl_dst_target );
add_ijump( $output_option_chainref, j => $chainref->{name} );
$output_option_chainref = $chainref,
}
}
if ( $setting & DBL_CLASSIC ) {
$chainref = new_standard_chain( $classic_target = nodbl_classic_chain( $interface ));
$chainref = new_standard_chain( nodbl_classic_chain( $interface ));
for (@nodbl) {
add_ijump( $chainref, j => 'RETURN', s => $_ );
@ -1018,13 +1031,15 @@ sub add_common_rules ( $ ) {
}
add_ijump( $chainref, j => $dynamicref->{name} );
$classic_target_chain = $chainref;
}
} else {
#
# Easy case
#
@src_exclude = ( s => "! $nodbl[0]" );
@dst_exclude = ( d => "! $nodbl[0]" );
@matches = ( s => "! $nodbl[0]" , @matches );
@matches = ( d => "! $nodbl[0]" , @matches );
}
}
@ -1033,27 +1048,34 @@ sub add_common_rules ( $ ) {
#
# src or src-dst
#
add_ipset_dbl_ijump( $filter_table->{input_option_chain($interface)}, $src_target, "$dbl_ipset src", @state );
add_ipset_dbl_ijump( $filter_table->{forward_option_chain($interface)}, $src_target, "$dbl_ipset src", @state );
add_ipset_dbl_ijump( $input_option_chainref, $dbl_src_target, "$dbl_ipset src", @matches );
add_ipset_dbl_ijump( $forward_option_chainref, $dbl_src_target, "$dbl_ipset src", @matches );
}
if ( $setting & DBL_DST ) {
#
# src-dst
#
add_ipset_dbl_ijump( $filter_table->{forward_option_chain($interface)}, $dst_target, "$dbl_ipset dst", @state );
add_ipset_dbl_ijump( $filter_table->{output_option_chain($interface)}, $dst_target, "$dbl_ipset dst", @state );
add_ipset_dbl_ijump( $forward_option_chainref, $dbl_dst_target, "$dbl_ipset dst", @matches );
add_ipset_dbl_ijump( $output_option_chainref, $dbl_dst_target, "$dbl_ipset dst", @matches );
}
}
if ( $setting & DBL_CLASSIC ) {
add_ijump_extended( $input_option_chainref, j => $classic_target_chain, $origin{DYNAMIC_BLACKLIST}, @state );
add_ijump_extended( $forward_option_chainref, j => $classic_target_chain, $origin{DYNAMIC_BLACKLIST}, @state );
add_ijump_extended( $output_option_chainref, j => $classic_target_chain, $origin{DYNAMIC_BLACKLIST}, @state ) if $setting & DBL_DST;
}
}
#
# Finish blacklisting and FASTACCEPT
# Finish FASTACCEPT
#
for ( option_chains( $interface ) ) {
add_ijump_extended( $filter_table->{$_}, j => $classic_target, $origin{DYNAMIC_BLACKLIST}, @state ) if $dynamicref && ( get_interface_option( $interface, 'dbl' ) & DBL_CLASSIC );
add_ijump_extended( $filter_table->{$_}, j => 'ACCEPT', $origin{FASTACCEPT}, state_imatch $faststate )->{comment} = '' if $config{FASTACCEPT};
if ( $config{FASTACCEPT} ) {
for ( option_chains( $interface ) ) {
add_ijump_extended( $filter_table->{$_}, j => 'ACCEPT', $origin{FASTACCEPT}, state_imatch $faststate )->{comment} = '';
}
}
}
} #Not loopback interface
}
#
# Delete 'sfilter' chains unless there are referenced to them

View File

@ -748,7 +748,7 @@ sub process_a_policy1($$$$$$$) {
fatal_error "NONE policy not allowed to/from firewall zone"
if ( zone_type( $client ) == FIREWALL ) || ( zone_type( $server ) == FIREWALL );
} elsif ( $policy eq 'BLACKLIST' ) {
fatal_error 'BLACKLIST policies require ipset-based dynamic blacklisting' unless $config{DYNAMIC_BLACKLIST} =~ /^ipset/;
fatal_error 'BLACKLIST policies require ipset-based dynamic blacklisting' unless $globals{DBL} & DBL_IPSET;
}
unless ( $clientwild || $serverwild ) {
@ -1078,12 +1078,10 @@ sub add_policy_rules( $$$$$ ) {
assert( $target );
if ( $target eq 'BLACKLIST' ) {
my ( $dbl_type, $dbl_ipset, $dbl_level, $dbl_tag ) = split( ':', $config{DYNAMIC_BLACKLIST} );
if ( my $timeout = $globals{DBL_TIMEOUT} ) {
add_ijump( $chainref, j => "SET --add-set $dbl_ipset src --exist --timeout $timeout" );
add_ijump( $chainref, j => "SET --add-set $globals{DBL_IPSET_NAME} src --exist --timeout $globals{DBL_TIMEOUT}" );
} else {
add_ijump( $chainref, j => "SET --add-set $dbl_ipset src --exist" );
add_ijump( $chainref, j => "SET --add-set $globals{DBL_IPSET_NAME} src --exist" );
}
$target = 'DROP';

View File

@ -49,11 +49,6 @@ our @EXPORT = ( qw( NOTHING
GROUP
NO_UPDOWN
NO_SFILTER
DBL_NONE
DBL_SRC
DBL_DST
DBL_SRC_DST
DBL_CLASSIC
determine_zones
zone_report
@ -219,16 +214,6 @@ use constant { NOTHING => 'NOTHING',
IPSECMODE => 'tunnel|transport'
};
#
# Dynamic blacklisting values
#
use constant { DBL_NONE => 0,
DBL_SRC => 1,
DBL_DST => 2,
DBL_SRC_DST => 3,
DBL_CLASSIC => 4,
};
sub NETWORK() {
$family == F_IPV4 ? '\d+.\d+.\d+.\d+(\/\d+)?' : '(?:[0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}(?:\/d+)?';
}
@ -1337,17 +1322,7 @@ sub process_interface( $$ ) {
$options{port} = 1 if $port;
my $setting = DBL_NONE;
if ( my $dbl = $config{DYNAMIC_BLACKLIST} ) {
unless ( $dbl =~ /^No/i ) {
$setting |= DBL_SRC;
$setting |= DBL_CLASSIC unless ( $dbl =~ /^ipset-only/ );
$setting |= DBL_DST if ( $dbl =~ /,(src-)?dst[,:]/ );
}
}
$options{dbl} = $setting;
$options{dbl} = $globals{DBL};
my $hostoptionsref = {};
@ -1419,7 +1394,7 @@ sub process_interface( $$ ) {
if ( $value eq 'none' ) {
$options{dbl} = DBL_NONE;
} else {
fatal_error qq(Invalid setting ($value) for 'dbl') unless defined ( $setting = $values{$value} );
fatal_error qq(Invalid setting ($value) for 'dbl') unless defined ( my $setting = $values{$value} );
$options{dbl} |= $setting;
}
} else {