public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [RFC firewall] implement fail2ban in firewall
@ 2021-08-23 14:07 Oguz Bektas
  2021-08-24 18:58 ` Thomas Lamprecht
  0 siblings, 1 reply; 5+ messages in thread
From: Oguz Bektas @ 2021-08-23 14:07 UTC (permalink / raw)
  To: pve-devel

only as POC/RFC

Signed-off-by: Oguz Bektas <o.bektas@proxmox.com>
---

known issues:
- see FIXME in generate_fail2ban_config. when update/compile is called
  the fail2ban service will be restarted, that leads to reload every 10s
  (also the jail conf file is re-written, which i'd like to avoid)
- no API integration yet. add to host.fw:

==============
[FAIL2BAN]
maxretry: N
bantime: N # minutes
==============


 debian/control      |  1 +
 src/PVE/Firewall.pm | 81 ++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/debian/control b/debian/control
index 4684c5b..377c9ae 100644
--- a/debian/control
+++ b/debian/control
@@ -17,6 +17,7 @@ Package: pve-firewall
 Architecture: any
 Conflicts: ulogd,
 Depends: ebtables,
+         fail2ban,
          ipset,
          iptables,
          libpve-access-control,
diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
index edc5336..73ae396 100644
--- a/src/PVE/Firewall.pm
+++ b/src/PVE/Firewall.pm
@@ -1347,6 +1347,26 @@ our $host_option_properties = {
     },
 };
 
+our $fail2ban_option_properties = {
+	enable => {
+	    description => "Enable or disable fail2ban on a node.",
+	    type => 'boolean',
+	    default => 1,
+	},
+	maxretry => {
+	    description => "Amount of failed tries to ban after.",
+	    type => 'integer',
+	    minimum => 1,
+	    default => 3,
+	},
+	bantime => {
+	    description => "Minutes to ban suspicious IPs.",
+	    type => 'integer',
+	    minimum => 1,
+	    default => 5,
+	},
+};
+
 our $vm_option_properties = {
     enable => {
 	description => "Enable/disable firewall rules.",
@@ -2407,6 +2427,30 @@ sub ruleset_generate_vm_rules {
     }
 }
 
+sub generate_fail2ban_config {
+    my ($maxretry, $bantime) = @_;
+
+    my $fail2ban_filter = "
+[Definition]
+failregex = pvedaemon\\[.*authentication failure; rhost=<HOST> user=.* msg=.*
+ignoreregex =\n";
+    my $filter_path = '/etc/fail2ban/filter.d/proxmox.conf';
+    PVE::Tools::file_set_contents($filter_path, $fail2ban_filter);
+
+    my $fail2ban_config = "
+[proxmox]
+enabled = true
+port = https,http,8006
+filter = proxmox
+logpath = /var/log/daemon.log
+maxretry = $maxretry
+bantime = $bantime\n";
+
+    my $jail_path = '/etc/fail2ban/jail.d/proxmox.conf';
+    PVE::Tools::file_set_contents($jail_path, $fail2ban_config);
+    #run_command([qw(systemctl try-reload-or-restart fail2ban.service)]); # FIXME: excessive... gets called by update/compile every 10 seconds so disable for now
+}
+
 sub generate_nfqueue {
     my ($options) = @_;
 
@@ -2937,6 +2981,22 @@ sub parse_alias {
     return undef;
 }
 
+sub parse_fail2ban_option {
+    my ($line) = @_;
+
+    my ($opt, $value);
+
+    if ($line =~ m/^(maxretry):\s+(\d+)\S*$/) {
+	$opt = $1;
+	$value = $2 // $fail2ban_option_properties->{maxretry}->{default};
+    } elsif ($line =~ m/^(bantime):\s+(\d+)\S*$/) {
+	$opt = $1;
+	$value = $2 * 60 // $fail2ban_option_properties->{bantime}->{default} * 60;
+    }
+
+    return ($opt, $value);
+}
+
 sub generic_fw_config_parser {
     my ($filename, $cluster_conf, $empty_conf, $rule_env) = @_;
 
@@ -2965,6 +3025,11 @@ sub generic_fw_config_parser {
 
 	my $prefix = "$filename (line $linenr)";
 
+	if ($empty_conf->{fail2ban} && ($line =~ m/^\[fail2ban\]$/i)) {
+	    $section = 'fail2ban';
+	    next;
+	}
+
 	if ($empty_conf->{options} && ($line =~ m/^\[options\]$/i)) {
 	    $section = 'options';
 	    next;
@@ -3046,6 +3111,12 @@ sub generic_fw_config_parser {
 		$res->{aliases}->{lc($data->{name})} = $data;
 	    };
 	    warn "$prefix: $@" if $@;
+	} elsif ($section eq 'fail2ban') {
+	    my ($opt, $value);
+	    eval {
+		($opt, $value) = parse_fail2ban_option($line);
+		$res->{fail2ban}->{$opt} = $value;
+	    };
 	} elsif ($section eq 'rules') {
 	    my $rule;
 	    eval { $rule = parse_fw_rule($prefix, $line, $cluster_conf, $res, $rule_env); };
@@ -3620,7 +3691,7 @@ sub load_hostfw_conf {
 
     $filename = $hostfw_conf_filename if !defined($filename);
 
-    my $empty_conf = { rules => [], options => {}};
+    my $empty_conf = { rules => [], options => {}, fail2ban => {}};
     return generic_fw_config_parser($filename, $cluster_conf, $empty_conf, 'host');
 }
 
@@ -4575,10 +4646,15 @@ sub init {
 
     return if !$enable;
 
+
     # load required modules here
 }
 
 sub update {
+
+    use Sys::Syslog;
+    syslog('info', "\n\nupdating stuff again...\n");
+
     my $code = sub {
 
 	my $cluster_conf = load_clusterfw_conf();
@@ -4589,7 +4665,10 @@ sub update {
 	    return;
 	}
 
+
 	my $hostfw_conf = load_hostfw_conf($cluster_conf);
+	my $fail2ban_opts = $hostfw_conf->{fail2ban};
+	generate_fail2ban_config($fail2ban_opts->{maxretry}, $fail2ban_opts->{bantime});
 
 	my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = compile($cluster_conf, $hostfw_conf);
 
-- 
2.30.2





^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [pve-devel] [RFC firewall] implement fail2ban in firewall
  2021-08-23 14:07 [pve-devel] [RFC firewall] implement fail2ban in firewall Oguz Bektas
@ 2021-08-24 18:58 ` Thomas Lamprecht
  2021-08-25  9:34   ` Oguz Bektas
  0 siblings, 1 reply; 5+ messages in thread
From: Thomas Lamprecht @ 2021-08-24 18:58 UTC (permalink / raw)
  To: Proxmox VE development discussion, Oguz Bektas

On 23/08/2021 16:07, Oguz Bektas wrote:
> only as POC/RFC
> 

some thoughts/discussions about the methods/decisions you made would get here.

E.g., did you think about checking the log just directly here, after all we run
every 10s anyway, so one could just directly parse the daemon log and add the
rules directly here, no extra daemon and external instance, which adapts filter
rules, required (the latter is racy anyway). Not necessarily a must, but the
simple regex on a single file would be easy, hardest thing would be to handle
rotations and make reading not completely inefficient; but neither to complicated
either.

any how, does fail2ban always flushes all their rules, as else our rewrite of
the filter and raw tables on each update would make it somewhat moot?

Still a few comments for review of that approach, which does not means that
it is the right approach (see above), but nonetheless, check them out.

> Signed-off-by: Oguz Bektas <o.bektas@proxmox.com>
> ---
> 
> known issues:
> - see FIXME in generate_fail2ban_config. when update/compile is called
>   the fail2ban service will be restarted, that leads to reload every 10s
>   (also the jail conf file is re-written, which i'd like to avoid)
> - no API integration yet. add to host.fw:
> 
> ==============
> [FAIL2BAN]
> maxretry: N
> bantime: N # minutes

your parser would not support above line with the comment though, as
/^(bantime):\s+(\d+)\S*$/

would not allow any whitespace (but arbitrary non-whitespace???) characters
after the integer..

> ==============
> 
> 
>  debian/control      |  1 +
>  src/PVE/Firewall.pm | 81 ++++++++++++++++++++++++++++++++++++++++++++-
>  2 files changed, 81 insertions(+), 1 deletion(-)
> 
> diff --git a/debian/control b/debian/control
> index 4684c5b..377c9ae 100644
> --- a/debian/control
> +++ b/debian/control
> @@ -17,6 +17,7 @@ Package: pve-firewall
>  Architecture: any
>  Conflicts: ulogd,
>  Depends: ebtables,
> +         fail2ban,

meh, would prefer to avoid a hard dependency on that package, rather use "Suggests" and
catch + error out if it isn't available.

>           ipset,
>           iptables,
>           libpve-access-control,
> diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
> index edc5336..73ae396 100644
> --- a/src/PVE/Firewall.pm
> +++ b/src/PVE/Firewall.pm
> @@ -1347,6 +1347,26 @@ our $host_option_properties = {
>      },
>  };
>  
> +our $fail2ban_option_properties = {
> +	enable => {
> +	    description => "Enable or disable fail2ban on a node.",
> +	    type => 'boolean',
> +	    default => 1,
> +	},
> +	maxretry => {
> +	    description => "Amount of failed tries to ban after.",
> +	    type => 'integer',
> +	    minimum => 1,
> +	    default => 3,
> +	},
> +	bantime => {
> +	    description => "Minutes to ban suspicious IPs.",
> +	    type => 'integer',
> +	    minimum => 1,
> +	    default => 5,
> +	},
> +};
> +
>  our $vm_option_properties = {
>      enable => {
>  	description => "Enable/disable firewall rules.",
> @@ -2407,6 +2427,30 @@ sub ruleset_generate_vm_rules {
>      }
>  }
>  
> +sub generate_fail2ban_config {
> +    my ($maxretry, $bantime) = @_;
> +
> +    my $fail2ban_filter = "
> +[Definition]
> +failregex = pvedaemon\\[.*authentication failure; rhost=<HOST> user=.* msg=.*
> +ignoreregex =\n";
> +    my $filter_path = '/etc/fail2ban/filter.d/proxmox.conf';

unnecessary intermediate variable that is only used once locally, just use the path
directly..

> +    PVE::Tools::file_set_contents($filter_path, $fail2ban_filter);
> +
> +    my $fail2ban_config = "
> +[proxmox]
> +enabled = true
> +port = https,http,8006
> +filter = proxmox
> +logpath = /var/log/daemon.log
> +maxretry = $maxretry
> +bantime = $bantime\n";

we normally use heredoc strings for such multiline stuff

> +
> +    my $jail_path = '/etc/fail2ban/jail.d/proxmox.conf';
> +    PVE::Tools::file_set_contents($jail_path, $fail2ban_config);
> +    #run_command([qw(systemctl try-reload-or-restart fail2ban.service)]); # FIXME: excessive... gets called by update/compile every 10 seconds so disable for now

you can simply check if the existing configuration, if any, and the one you write
above are the same and use that to decide if a reload would be required.

> +}
> +
>  sub generate_nfqueue {
>      my ($options) = @_;
>  
> @@ -2937,6 +2981,22 @@ sub parse_alias {
>      return undef;
>  }
>  
> +sub parse_fail2ban_option {
> +    my ($line) = @_;
> +
> +    my ($opt, $value);
> +
> +    if ($line =~ m/^(maxretry):\s+(\d+)\S*$/) {

regex seems wrong, why the non-whitespace match at the end?

> +	$opt = $1;
> +	$value = $2 // $fail2ban_option_properties->{maxretry}->{default};
> +    } elsif ($line =~ m/^(bantime):\s+(\d+)\S*$/) {

regex seems wrong, why the non-whitespace match at the end?

> +	$opt = $1;
> +	$value = $2 * 60 // $fail2ban_option_properties->{bantime}->{default} * 60;

why duplicate the multiplication?

$value = ($2 // $fail2ban_option_properties->{bantime}->{default}) * 60;


or better, do the `* 60` only when writing the fail2ban config and then you could
use a single if here, i.e. something along the lines of:

if ($line =~ m/^(maxretry|bantime):\s+(\d+)\S*$/) {
   return ($1, $2 // $fail2ban_option_properties->{$1}->{default})
}

> +    }
> +
> +    return ($opt, $value);
> +}
> +
>  sub generic_fw_config_parser {
>      my ($filename, $cluster_conf, $empty_conf, $rule_env) = @_;
>  
> @@ -2965,6 +3025,11 @@ sub generic_fw_config_parser {
>  
>  	my $prefix = "$filename (line $linenr)";
>  
> +	if ($empty_conf->{fail2ban} && ($line =~ m/^\[fail2ban\]$/i)) {
> +	    $section = 'fail2ban';
> +	    next;
> +	}
> +
>  	if ($empty_conf->{options} && ($line =~ m/^\[options\]$/i)) {
>  	    $section = 'options';
>  	    next;
> @@ -3046,6 +3111,12 @@ sub generic_fw_config_parser {
>  		$res->{aliases}->{lc($data->{name})} = $data;
>  	    };
>  	    warn "$prefix: $@" if $@;
> +	} elsif ($section eq 'fail2ban') {
> +	    my ($opt, $value);
> +	    eval {
> +		($opt, $value) = parse_fail2ban_option($line);
> +		$res->{fail2ban}->{$opt} = $value;
> +	    };

got one line evals we try to assign the result directly nowadays, i.e., something like:

my ($opt, $value) = eval { parse_fail2ban_option($line) };
if (my $err = $@) {
    warn "fail2ban parsing error: $err"
    next;
}
$res->{fail2ban}->{$opt} = $value;

would be nicer to read and provide better UX.. but why eval it in the first place if
you never can die in parse_fail2ban_option anyway?

>  	} elsif ($section eq 'rules') {
>  	    my $rule;
>  	    eval { $rule = parse_fw_rule($prefix, $line, $cluster_conf, $res, $rule_env); };
> @@ -3620,7 +3691,7 @@ sub load_hostfw_conf {
>  
>      $filename = $hostfw_conf_filename if !defined($filename);
>  
> -    my $empty_conf = { rules => [], options => {}};
> +    my $empty_conf = { rules => [], options => {}, fail2ban => {}};
>      return generic_fw_config_parser($filename, $cluster_conf, $empty_conf, 'host');
>  }
>  
> @@ -4575,10 +4646,15 @@ sub init {
>  
>      return if !$enable;
>  
> +

adding unrelated new line

>      # load required modules here
>  }
>  
>  sub update {
> +
> +    use Sys::Syslog;
> +    syslog('info', "\n\nupdating stuff again...\n");
> +

left over debug print

>      my $code = sub {
>  
>  	my $cluster_conf = load_clusterfw_conf();
> @@ -4589,7 +4665,10 @@ sub update {
>  	    return;
>  	}
>  
> +

adding unecessarry new line

>  	my $hostfw_conf = load_hostfw_conf($cluster_conf);
> +	my $fail2ban_opts = $hostfw_conf->{fail2ban};
> +	generate_fail2ban_config($fail2ban_opts->{maxretry}, $fail2ban_opts->{bantime});
>  
>  	my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = compile($cluster_conf, $hostfw_conf);
>  
> 





^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [pve-devel] [RFC firewall] implement fail2ban in firewall
  2021-08-24 18:58 ` Thomas Lamprecht
@ 2021-08-25  9:34   ` Oguz Bektas
  2021-08-25 12:51     ` Thomas Lamprecht
  0 siblings, 1 reply; 5+ messages in thread
From: Oguz Bektas @ 2021-08-25  9:34 UTC (permalink / raw)
  To: Thomas Lamprecht; +Cc: Proxmox VE development discussion

hi,

thank you for the review :)

On Tue, Aug 24, 2021 at 08:58:10PM +0200, Thomas Lamprecht wrote:
> On 23/08/2021 16:07, Oguz Bektas wrote:
> > only as POC/RFC
> > 
> 
> some thoughts/discussions about the methods/decisions you made would get here.
> 
> E.g., did you think about checking the log just directly here, after all we run
> every 10s anyway, so one could just directly parse the daemon log and add the
> rules directly here, no extra daemon and external instance, which adapts filter
> rules, required (the latter is racy anyway). Not necessarily a must, but the
> simple regex on a single file would be easy, hardest thing would be to handle
> rotations and make reading not completely inefficient; but neither to complicated
> either.

in the v2 it works better after implementing your suggestions (i'll send
it today), now we check if the jail file has changed and only write it then.

> 
> any how, does fail2ban always flushes all their rules, as else our rewrite of
> the filter and raw tables on each update would make it somewhat moot?

i'm not exactly sure, but in my tests the banned IP addresses stayed
even after changing configuration and reloading the services. you can
check with `fail2ban-client banned`.

> 
> Still a few comments for review of that approach, which does not means that
> it is the right approach (see above), but nonetheless, check them out.
> 
> > Signed-off-by: Oguz Bektas <o.bektas@proxmox.com>
> > ---
> > 
> > known issues:
> > - see FIXME in generate_fail2ban_config. when update/compile is called
> >   the fail2ban service will be restarted, that leads to reload every 10s
> >   (also the jail conf file is re-written, which i'd like to avoid)
> > - no API integration yet. add to host.fw:
> > 
> > ==============
> > [FAIL2BAN]
> > maxretry: N
> > bantime: N # minutes
> 
> your parser would not support above line with the comment though, as
> /^(bantime):\s+(\d+)\S*$/

fixed in v2 along with the other suggestions

> 
> would not allow any whitespace (but arbitrary non-whitespace???) characters
> after the integer..
> 
> > ==============
> > 
> > 
> >  debian/control      |  1 +
> >  src/PVE/Firewall.pm | 81 ++++++++++++++++++++++++++++++++++++++++++++-
> >  2 files changed, 81 insertions(+), 1 deletion(-)
> > 
> > diff --git a/debian/control b/debian/control
> > index 4684c5b..377c9ae 100644
> > --- a/debian/control
> > +++ b/debian/control
> > @@ -17,6 +17,7 @@ Package: pve-firewall
> >  Architecture: any
> >  Conflicts: ulogd,
> >  Depends: ebtables,
> > +         fail2ban,
> 
> meh, would prefer to avoid a hard dependency on that package, rather use "Suggests" and
> catch + error out if it isn't available.


ah well, i've enabled it for default, that's why i added it as a hard
dependency.

> 
> >           ipset,
> >           iptables,
> >           libpve-access-control,
> > diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
> > index edc5336..73ae396 100644
> > --- a/src/PVE/Firewall.pm
> > +++ b/src/PVE/Firewall.pm
> > @@ -1347,6 +1347,26 @@ our $host_option_properties = {
> >      },
> >  };
> >  
> > +our $fail2ban_option_properties = {
> > +	enable => {
> > +	    description => "Enable or disable fail2ban on a node.",
> > +	    type => 'boolean',
> > +	    default => 1,
> > +	},
> > +	maxretry => {
> > +	    description => "Amount of failed tries to ban after.",
> > +	    type => 'integer',
> > +	    minimum => 1,
> > +	    default => 3,
> > +	},
> > +	bantime => {
> > +	    description => "Minutes to ban suspicious IPs.",
> > +	    type => 'integer',
> > +	    minimum => 1,
> > +	    default => 5,
> > +	},
> > +};
> > +
> >  our $vm_option_properties = {
> >      enable => {
> >  	description => "Enable/disable firewall rules.",
> > @@ -2407,6 +2427,30 @@ sub ruleset_generate_vm_rules {
> >      }
> >  }
> >  
> > +sub generate_fail2ban_config {
> > +    my ($maxretry, $bantime) = @_;
> > +
> > +    my $fail2ban_filter = "
> > +[Definition]
> > +failregex = pvedaemon\\[.*authentication failure; rhost=<HOST> user=.* msg=.*
> > +ignoreregex =\n";
> > +    my $filter_path = '/etc/fail2ban/filter.d/proxmox.conf';
> 
> unnecessary intermediate variable that is only used once locally, just use the path
> directly..
> 
> > +    PVE::Tools::file_set_contents($filter_path, $fail2ban_filter);
> > +
> > +    my $fail2ban_config = "
> > +[proxmox]
> > +enabled = true
> > +port = https,http,8006
> > +filter = proxmox
> > +logpath = /var/log/daemon.log
> > +maxretry = $maxretry
> > +bantime = $bantime\n";
> 
> we normally use heredoc strings for such multiline stuff
> 
> > +
> > +    my $jail_path = '/etc/fail2ban/jail.d/proxmox.conf';
> > +    PVE::Tools::file_set_contents($jail_path, $fail2ban_config);
> > +    #run_command([qw(systemctl try-reload-or-restart fail2ban.service)]); # FIXME: excessive... gets called by update/compile every 10 seconds so disable for now
> 
> you can simply check if the existing configuration, if any, and the one you write
> above are the same and use that to decide if a reload would be required.
> 
> > +}
> > +
> >  sub generate_nfqueue {
> >      my ($options) = @_;
> >  
> > @@ -2937,6 +2981,22 @@ sub parse_alias {
> >      return undef;
> >  }
> >  
> > +sub parse_fail2ban_option {
> > +    my ($line) = @_;
> > +
> > +    my ($opt, $value);
> > +
> > +    if ($line =~ m/^(maxretry):\s+(\d+)\S*$/) {
> 
> regex seems wrong, why the non-whitespace match at the end?
> 
> > +	$opt = $1;
> > +	$value = $2 // $fail2ban_option_properties->{maxretry}->{default};
> > +    } elsif ($line =~ m/^(bantime):\s+(\d+)\S*$/) {
> 
> regex seems wrong, why the non-whitespace match at the end?
> 
> > +	$opt = $1;
> > +	$value = $2 * 60 // $fail2ban_option_properties->{bantime}->{default} * 60;
> 
> why duplicate the multiplication?
> 
> $value = ($2 // $fail2ban_option_properties->{bantime}->{default}) * 60;
> 
> 
> or better, do the `* 60` only when writing the fail2ban config and then you could
> use a single if here, i.e. something along the lines of:
> 
> if ($line =~ m/^(maxretry|bantime):\s+(\d+)\S*$/) {
>    return ($1, $2 // $fail2ban_option_properties->{$1}->{default})
> }
> 
> > +    }
> > +
> > +    return ($opt, $value);
> > +}
> > +
> >  sub generic_fw_config_parser {
> >      my ($filename, $cluster_conf, $empty_conf, $rule_env) = @_;
> >  
> > @@ -2965,6 +3025,11 @@ sub generic_fw_config_parser {
> >  
> >  	my $prefix = "$filename (line $linenr)";
> >  
> > +	if ($empty_conf->{fail2ban} && ($line =~ m/^\[fail2ban\]$/i)) {
> > +	    $section = 'fail2ban';
> > +	    next;
> > +	}
> > +
> >  	if ($empty_conf->{options} && ($line =~ m/^\[options\]$/i)) {
> >  	    $section = 'options';
> >  	    next;
> > @@ -3046,6 +3111,12 @@ sub generic_fw_config_parser {
> >  		$res->{aliases}->{lc($data->{name})} = $data;
> >  	    };
> >  	    warn "$prefix: $@" if $@;
> > +	} elsif ($section eq 'fail2ban') {
> > +	    my ($opt, $value);
> > +	    eval {
> > +		($opt, $value) = parse_fail2ban_option($line);
> > +		$res->{fail2ban}->{$opt} = $value;
> > +	    };
> 
> got one line evals we try to assign the result directly nowadays, i.e., something like:
> 
> my ($opt, $value) = eval { parse_fail2ban_option($line) };
> if (my $err = $@) {
>     warn "fail2ban parsing error: $err"
>     next;
> }
> $res->{fail2ban}->{$opt} = $value;
> 
> would be nicer to read and provide better UX.. but why eval it in the first place if
> you never can die in parse_fail2ban_option anyway?
> 
> >  	} elsif ($section eq 'rules') {
> >  	    my $rule;
> >  	    eval { $rule = parse_fw_rule($prefix, $line, $cluster_conf, $res, $rule_env); };
> > @@ -3620,7 +3691,7 @@ sub load_hostfw_conf {
> >  
> >      $filename = $hostfw_conf_filename if !defined($filename);
> >  
> > -    my $empty_conf = { rules => [], options => {}};
> > +    my $empty_conf = { rules => [], options => {}, fail2ban => {}};
> >      return generic_fw_config_parser($filename, $cluster_conf, $empty_conf, 'host');
> >  }
> >  
> > @@ -4575,10 +4646,15 @@ sub init {
> >  
> >      return if !$enable;
> >  
> > +
> 
> adding unrelated new line
> 
> >      # load required modules here
> >  }
> >  
> >  sub update {
> > +
> > +    use Sys::Syslog;
> > +    syslog('info', "\n\nupdating stuff again...\n");
> > +
> 
> left over debug print
> 
> >      my $code = sub {
> >  
> >  	my $cluster_conf = load_clusterfw_conf();
> > @@ -4589,7 +4665,10 @@ sub update {
> >  	    return;
> >  	}
> >  
> > +
> 
> adding unecessarry new line
> 
> >  	my $hostfw_conf = load_hostfw_conf($cluster_conf);
> > +	my $fail2ban_opts = $hostfw_conf->{fail2ban};
> > +	generate_fail2ban_config($fail2ban_opts->{maxretry}, $fail2ban_opts->{bantime});
> >  
> >  	my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = compile($cluster_conf, $hostfw_conf);
> >  
> > 
> 




^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [pve-devel] [RFC firewall] implement fail2ban in firewall
  2021-08-25  9:34   ` Oguz Bektas
@ 2021-08-25 12:51     ` Thomas Lamprecht
  2021-10-11 10:50       ` Oguz Bektas
  0 siblings, 1 reply; 5+ messages in thread
From: Thomas Lamprecht @ 2021-08-25 12:51 UTC (permalink / raw)
  To: Oguz Bektas, Proxmox VE development discussion

On 25/08/2021 11:34, Oguz Bektas wrote:
> On Tue, Aug 24, 2021 at 08:58:10PM +0200, Thomas Lamprecht wrote:
>> E.g., did you think about checking the log just directly here, after all we run
>> every 10s anyway, so one could just directly parse the daemon log and add the
>> rules directly here, no extra daemon and external instance, which adapts filter
>> rules, required (the latter is racy anyway). Not necessarily a must, but the
>> simple regex on a single file would be easy, hardest thing would be to handle
>> rotations and make reading not completely inefficient; but neither to complicated
>> either.
> 
> in the v2 it works better after implementing your suggestions (i'll send
> it today), now we check if the jail file has changed and only write it then.
> 

that has zero to do with thinking and evaluating about just doing it ourself here
though? As there's still an additional dependency with an extra daemon running that
may not even interact correctly with how we operate the iptables...

>>
>> any how, does fail2ban always flushes all their rules, as else our rewrite of
>> the filter and raw tables on each update would make it somewhat moot?
> 
> i'm not exactly sure, but in my tests the banned IP addresses stayed
> even after changing configuration and reloading the services. you can
> check with `fail2ban-client banned`.

I did not asked about the banned IP address view from the fail2ban daemon but
rather if the actual *iptables* rules persist, would be good to get sure about
such stuff if wanting to integrate such a feature..




^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [pve-devel] [RFC firewall] implement fail2ban in firewall
  2021-08-25 12:51     ` Thomas Lamprecht
@ 2021-10-11 10:50       ` Oguz Bektas
  0 siblings, 0 replies; 5+ messages in thread
From: Oguz Bektas @ 2021-10-11 10:50 UTC (permalink / raw)
  To: Thomas Lamprecht; +Cc: Proxmox VE development discussion

hi,

> 
> that has zero to do with thinking and evaluating about just doing it ourself here
> though? As there's still an additional dependency with an extra daemon running that
> may not even interact correctly with how we operate the iptables...

fail2ban already does all of this, is supported and recommended in our wiki
with an example config that's also used in this patch, imho it makes sense to
just use it instead of reinventing the wheel here. surely it wouldn't be hard
to add a reject rule into the chain, but there are other things to think about
as well.

this approach also has the benefit for users who already have our fail2ban
config, who should be able to use this without any interference. whereas if we
separately try to implement the parsing of logs, rotating them, adding banned
addresses to firewall chain (and keeping timestamps of ban times), unbanning them
automatically after 'bantime' expires, there's a lot of things to handle that
i'm also forgetting here... so it's not as simple as one would imagine :)


> 
> >>
> >> any how, does fail2ban always flushes all their rules, as else our rewrite of
> >> the filter and raw tables on each update would make it somewhat moot?
> > 
> > i'm not exactly sure, but in my tests the banned IP addresses stayed
> > even after changing configuration and reloading the services. you can
> > check with `fail2ban-client banned`.
> 
> I did not asked about the banned IP address view from the fail2ban daemon but
> rather if the actual *iptables* rules persist, would be good to get sure about
> such stuff if wanting to integrate such a feature..

it adds a new INPUT chain falled "f2b-proxmox" when a new rule is added (a.k.a. someone is banned after hitting 'maxretry')

$ iptables -L -v | grep -i f2b -C 3

Chain INPUT (policy ACCEPT 2 packets, 120 bytes)
 pkts bytes target     prot opt in     out     source               destination
  188 72943 f2b-proxmox  tcp  --  any    any     anywhere             anywhere             multiport dports https,http,8006
  466 91452 PVEFW-INPUT  all  --  any    any     anywhere             anywhere
...
Chain f2b-proxmox (1 references)
 pkts bytes target     prot opt in     out     source               destination
   10   520 REJECT     all  --  any    any     192.168.31.213       anywhere             reject-with icmp-port-unreachable
  178 72423 RETURN     all  --  any    any     anywhere             anywhere



the rules persist when restarting the firewall with "pve-firewall restart" or
via systemctl, they also persist with "pve-firewall compile" followed by
service restart. changing firewall rules also has no effect.
so without "systemctl stop fail2ban" i was unable to get rid of the chain and rules.

after starting it again the REJECT rules in the chain still persist (if bantime didn't expire for that entry).
after bantime is over the rule is removed (chain stays as long as service is running).

also i noticed a small bug in my last patch waiting for review, i'll send a fix for that with a new version.

regards,
oguz




^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-10-11 10:50 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-23 14:07 [pve-devel] [RFC firewall] implement fail2ban in firewall Oguz Bektas
2021-08-24 18:58 ` Thomas Lamprecht
2021-08-25  9:34   ` Oguz Bektas
2021-08-25 12:51     ` Thomas Lamprecht
2021-10-11 10:50       ` Oguz Bektas

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal