From 34cdedfa198177c526f65422e1afc8e2cde1673c Mon Sep 17 00:00:00 2001 From: Alexandre Derumier Date: Thu, 27 Mar 2014 11:22:06 +0100 Subject: [PATCH] implement ipset ip/net groups This implement ipset groups of ips or network in groups.fw. groups.fw --------- [ipgroup ipgroup1] 192.168.0.1 192.168.0.2 192.168.0.3 [ipgroup ipgroup2] 192.168.0.3 192.168.0.4 [netgroup netgroup1] 192.168.0.0/24 10.0.0.0/8 Signed-off-by: Alexandre Derumier --- example/groups.fw | 20 ++++++ src/PVE/Firewall.pm | 165 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 171 insertions(+), 14 deletions(-) diff --git a/example/groups.fw b/example/groups.fw index eab4b2d..b9c088f 100644 --- a/example/groups.fw +++ b/example/groups.fw @@ -10,3 +10,23 @@ IN ACCEPT 10.0.0.1 IN ACCEPT 10.0.0.2 IN ACCEPT 10.0.0.2 + +#ipset hash:ip +[ipgroup ipgroup1] + +192.168.0.1 +192.168.0.2 +192.168.0.3 + + +[ipgroup ipgroup2] + +192.168.0.3 +192.168.0.4 + +#ipset hash:net +[netgroup netgroup1] + +192.168.0.0/24 +10.0.0.0/8 + diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 630e5c4..b0bcd94 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -801,6 +801,12 @@ sub iptables_restore_cmdlist { run_command("/sbin/iptables-restore -n", input => $cmdlist); } +sub ipset_restore_cmdlist { + my ($cmdlist) = @_; + + run_command(" /usr/sbin/ipset restore", input => $cmdlist); +} + sub iptables_get_chains { my $res = {}; @@ -857,6 +863,38 @@ sub iptables_get_chains { return $res; } +sub ipset_get_chains { + + my $res = {}; + my $chains = {}; + + my $parser = sub { + my $line = shift; + + return if $line =~ m/^#/; + return if $line =~ m/^\s*$/; + if ($line =~ m/^(\S+)\s(\S+)\s(\S+)/) { + push @{$chains->{$2}}, $line; + } else { + # simply ignore the rest + return; + } + }; + + run_command(" /usr/sbin/ipset save", outfunc => $parser); + + #comptute sig for each chain + foreach my $chain (keys %$chains){ + my $digest = Digest::SHA->new('sha1'); + foreach my $rule (@{$chains->{$chain}}) { + $digest->add($rule); + } + $res->{$chain} = $digest->b64digest; + } + + return $res; +} + sub iptables_chain_exist { my ($chain) = @_; @@ -1709,7 +1747,7 @@ sub parse_group_fw_rules { my $section; my $group; - my $res = { rules => {} }; + my $res = { rules => {}, ipset => {} }; my $digest = Digest::SHA->new('sha1'); @@ -1727,19 +1765,46 @@ sub parse_group_fw_rules { $group = lc($1); next; } + + if ($line =~ m/^\[ipgroup\s+(\S+)\]\s*$/i) { + $section = 'ipset'; + $group = lc($1); + $res->{$section}->{$group}->{type} = 'hash:ip family inet hashsize 1024 maxelem 65536 '; + + next; + } + if ($line =~ m/^\[netgroup\s+(\S+)\]\s*$/i) { + $section = 'ipset'; + $group = lc($1); + $res->{$section}->{$group}->{type} = 'hash:net family inet hashsize 1024 maxelem 65536 '; + + next; + } + if (!$section || !$group) { warn "$prefix: skip line - no section"; next; } - my $rule; - eval { $rule = parse_fw_rule($line, 0, 0); }; - if (my $err = $@) { - warn "$prefix: $err"; - next; + if($section eq 'rules'){ + my $rule; + eval { $rule = parse_fw_rule($line, 0, 0); }; + if (my $err = $@) { + warn "$prefix: $err"; + next; + } + push @{$res->{$section}->{$group}}, $rule; + + }elsif($section eq 'ipset'){ + my $ip; + if (!Net::IP->new($line)) { + warn "$prefix: $line is not an valid ip address"; + next; + } + push @{$res->{$section}->{$group}->{ip}}, $line; } + - push @{$res->{$section}->{$group}}, $rule; } $res->{digest} = $digest->b64digest; @@ -1868,6 +1933,24 @@ sub generate_std_chains { } } +sub generate_ipset_chains { + my ($ipset_ruleset, $options) = @_; + + foreach my $ipset (keys %{$options->{ipset}}) { + generate_ipset($ipset_ruleset, $ipset, $options->{ipset}->{$ipset}); + } +} + +sub generate_ipset { + my ($ipset_ruleset, $name, $options) = @_; + + push @{$ipset_ruleset->{$name}}, "create $name $options->{type}"; + + foreach my $ip (@{$options->{ip}}) { + push @{$ipset_ruleset->{$name}}, "add $name $ip"; + } +} + sub save_pvefw_status { my ($status) = @_; @@ -1928,7 +2011,7 @@ sub load_security_groups { $groups_conf = parse_group_fw_rules($filename, $fh); } - return $groups_conf; + return ($groups_conf); } sub save_security_groups { @@ -1983,6 +2066,9 @@ sub compile { my $groups_conf = load_security_groups(); + my $ipset_ruleset = {}; + generate_ipset_chains($ipset_ruleset, $groups_conf); + my $ruleset = {}; ruleset_create_chain($ruleset, "PVEFW-INPUT"); @@ -2086,13 +2172,18 @@ sub compile { ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j DROP"); ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j DROP"); - return wantarray ? ($ruleset, $hostfw_conf) : $ruleset; + return wantarray ? ($ruleset, $hostfw_conf, $ipset_ruleset) : $ruleset; } sub get_ruleset_status { - my ($ruleset, $verbose) = @_; + my ($ruleset, $verbose, $ipset) = @_; - my $active_chains = iptables_get_chains(); + my $active_chains = undef; + if($ipset){ + $active_chains = ipset_get_chains(); + }else{ + $active_chains = iptables_get_chains(); + } my $statushash = {}; @@ -2211,17 +2302,63 @@ sub get_rulset_cmdlist { return $cmdlist; } +sub get_ipset_cmdlist { + my ($ruleset, $verbose) = @_; + + my $cmdlist = ""; + + my $statushash = get_ruleset_status($ruleset, $verbose, 1); + + foreach my $chain (sort keys %$ruleset) { + my $stat = $statushash->{$chain}; + die "internal error" if !$stat; + + if ($stat->{action} eq 'create') { + foreach my $cmd (@{$ruleset->{$chain}}) { + $cmdlist .= "$cmd\n"; + } + } + + if ($stat->{action} eq 'update') { + my $chain_swap = $chain."_swap"; + + foreach my $cmd (@{$ruleset->{$chain}}) { + $cmd =~ s/$chain/$chain_swap/; + $cmdlist .= "$cmd\n"; + + } + $cmdlist .= "swap $chain_swap $chain\n"; + $cmdlist .= "flush $chain_swap\n"; + $cmdlist .= "destroy $chain_swap\n"; + + } + + } + + foreach my $chain (keys %$statushash) { + next if $statushash->{$chain}->{action} ne 'delete'; + $cmdlist .= "flush $chain\n"; + $cmdlist .= "destroy $chain\n"; + } + + return $cmdlist; +} + sub apply_ruleset { - my ($ruleset, $hostfw_conf, $verbose) = @_; + my ($ruleset, $hostfw_conf, $ipset_ruleset, $verbose) = @_; enable_bridge_firewall(); update_nf_conntrack_max($hostfw_conf); + my $ipsetcmdlist = get_ipset_cmdlist($ipset_ruleset, $verbose); + my $cmdlist = get_rulset_cmdlist($ruleset, $verbose); print $cmdlist if $verbose; + ipset_restore_cmdlist($ipsetcmdlist); + iptables_restore_cmdlist($cmdlist); # test: re-read status and check if everything is up to date @@ -2269,13 +2406,13 @@ sub update { my $code = sub { my $status = read_pvefw_status(); - my ($ruleset, $hostfw_conf) = PVE::Firewall::compile(); + my ($ruleset, $hostfw_conf, $ipset_ruleset) = PVE::Firewall::compile(); if ($start || $status eq 'active') { save_pvefw_status('active') if ($status ne 'active'); - apply_ruleset($ruleset, $hostfw_conf, $verbose); + apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $verbose); } else { print "Firewall not active (status = $status)\n" if $verbose; } -- 2.39.2