X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FAPI2%2FFirewall%2FIPSet.pm;h=ec9326f475f77d271b26bbda272a59d22b1b5065;hp=6129c9d6f79c4fff9284f19240725c35bc643f25;hb=HEAD;hpb=5da1a22976974c6ffde189206f2a03760e8854d8 diff --git a/src/PVE/API2/Firewall/IPSet.pm b/src/PVE/API2/Firewall/IPSet.pm index 6129c9d..ed92d87 100644 --- a/src/PVE/API2/Firewall/IPSet.pm +++ b/src/PVE/API2/Firewall/IPSet.pm @@ -9,7 +9,7 @@ use PVE::Firewall; use base qw(PVE::RESTHandler); -my $api_properties = { +my $api_properties = { cidr => { description => "Network/IP specification in CIDR format.", type => 'string', format => 'IPorCIDRorAlias', @@ -25,6 +25,12 @@ my $api_properties = { }, }; +sub lock_config { + my ($class, $param, $code) = @_; + + die "implement this in subclass"; +} + sub load_config { my ($class, $param) = @_; @@ -41,7 +47,7 @@ sub save_config { sub rule_env { my ($class, $param) = @_; - + die "implement this in subclass"; } @@ -106,7 +112,7 @@ sub register_get_ipset { type => 'boolean', optional => 1, }, - digest => get_standard_option('pve-config-digest', { optional => 0} ), + digest => get_standard_option('pve-config-digest', { optional => 0} ), }, }, links => [ { rel => 'child', href => "{cidr}" } ], @@ -126,6 +132,11 @@ sub register_delete_ipset { my $properties = $class->additional_parameters(); $properties->{name} = get_standard_option('ipset-name'); + $properties->{force} = { + type => 'boolean', + optional => 1, + description => 'Delete all members of the IPSet, if there are any.', + }; $class->register_method({ name => 'delete_ipset', @@ -141,13 +152,18 @@ sub register_delete_ipset { returns => { type => 'null' }, code => sub { my ($param) = @_; - - my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); - die "IPSet '$param->{name}' is not empty\n" - if scalar(@$ipset); + $class->lock_config($param, sub { + my ($param) = @_; + + my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); - $class->save_ipset($param, $fw_conf, undef); + die "IPSet '$param->{name}' is not empty\n" + if scalar(@$ipset) && !$param->{force}; + + $class->save_ipset($param, $fw_conf, undef); + + }); return undef; }}); @@ -178,27 +194,42 @@ sub register_create_ip { code => sub { my ($param) = @_; - my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); + $class->lock_config($param, sub { + my ($param) = @_; + + my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); + + my $cidr = $param->{cidr}; + if ($cidr =~ m@^(dc/|guest/)?(${PVE::Firewall::ip_alias_pattern})$@) { + my $scope = $1 // ""; + my $alias = $2; + # make sure alias exists (if $cidr is an alias) + PVE::Firewall::resolve_alias($cluster_conf, $fw_conf, $alias, $scope); + } else { + $cidr = PVE::Firewall::clean_cidr($cidr); + # normalize like config parser, otherwise duplicates might slip through + $cidr = PVE::Firewall::parse_ip_or_cidr($cidr); + } + + foreach my $entry (@$ipset) { + raise_param_exc({ cidr => "address '$cidr' already exists" }) + if $entry->{cidr} eq $cidr; + } + + raise_param_exc({ cidr => "a zero prefix is not allowed in ipset entries" }) + if $cidr =~ m!/0+$!; - my $cidr = $param->{cidr}; - - foreach my $entry (@$ipset) { - raise_param_exc({ cidr => "address '$cidr' already exists" }) - if $entry->{cidr} eq $cidr; - } - # make sure alias exists (if $cidr is an alias) - PVE::Firewall::resolve_alias($cluster_conf, $fw_conf, $cidr) - if $cidr =~ m/^${PVE::Firewall::ip_alias_pattern}$/; + my $data = { cidr => $cidr }; - my $data = { cidr => $cidr }; + $data->{nomatch} = 1 if $param->{nomatch}; + $data->{comment} = $param->{comment} if $param->{comment}; - $data->{nomatch} = 1 if $param->{nomatch}; - $data->{comment} = $param->{comment} if $param->{comment}; + unshift @$ipset, $data; - unshift @$ipset, $data; + $class->save_ipset($param, $fw_conf, $ipset); - $class->save_ipset($param, $fw_conf, $ipset); + }); return undef; }}); @@ -211,7 +242,7 @@ sub register_read_ip { $properties->{name} = $api_properties->{name}; $properties->{cidr} = $api_properties->{cidr}; - + $class->register_method({ name => 'read_ip', path => '{cidr}', @@ -267,19 +298,27 @@ sub register_update_ip { code => sub { my ($param) = @_; - my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); + my $found = $class->lock_config($param, sub { + my ($param) = @_; - my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset); - PVE::Tools::assert_if_modified($digest, $param->{digest}); + my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); - foreach my $entry (@$ipset) { - if($entry->{cidr} eq $param->{cidr}) { - $entry->{nomatch} = $param->{nomatch}; - $entry->{comment} = $param->{comment}; - $class->save_ipset($param, $fw_conf, $ipset); - return; + my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset); + PVE::Tools::assert_if_modified($digest, $param->{digest}); + + foreach my $entry (@$ipset) { + if($entry->{cidr} eq $param->{cidr}) { + $entry->{nomatch} = $param->{nomatch}; + $entry->{comment} = $param->{comment}; + $class->save_ipset($param, $fw_conf, $ipset); + return 1; + } } - } + + return 0; + }); + + return if $found; raise_param_exc({ cidr => "no such IP/Network" }); }}); @@ -309,19 +348,23 @@ sub register_delete_ip { code => sub { my ($param) = @_; - my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); + $class->lock_config($param, sub { + my ($param) = @_; - my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset); - PVE::Tools::assert_if_modified($digest, $param->{digest}); + my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param); - my $new = []; - - foreach my $entry (@$ipset) { - push @$new, $entry if $entry->{cidr} ne $param->{cidr}; - } + my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset); + PVE::Tools::assert_if_modified($digest, $param->{digest}); + + my $new = []; + + foreach my $entry (@$ipset) { + push @$new, $entry if $entry->{cidr} ne $param->{cidr}; + } + + $class->save_ipset($param, $fw_conf, $new); + }); - $class->save_ipset($param, $fw_conf, $new); - return undef; }}); } @@ -346,10 +389,16 @@ use base qw(PVE::API2::Firewall::IPSetBase); sub rule_env { my ($class, $param) = @_; - + return 'cluster'; } +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_clusterfw_conf(10, $code, $param); +} + sub load_config { my ($class, $param) = @_; @@ -378,15 +427,21 @@ use base qw(PVE::API2::Firewall::IPSetBase); sub rule_env { my ($class, $param) = @_; - + return 'vm'; } -__PACKAGE__->additional_parameters({ +__PACKAGE__->additional_parameters({ node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid'), }); +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param); +} + sub load_config { my ($class, $param) = @_; @@ -416,15 +471,21 @@ use base qw(PVE::API2::Firewall::IPSetBase); sub rule_env { my ($class, $param) = @_; - + return 'ct'; } -__PACKAGE__->additional_parameters({ +__PACKAGE__->additional_parameters({ node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid'), }); +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param); +} + sub load_config { my ($class, $param) = @_; @@ -454,9 +515,15 @@ use PVE::Firewall; use base qw(PVE::RESTHandler); +sub lock_config { + my ($class, $param, $code) = @_; + + die "implement this in subclass"; +} + sub load_config { my ($class, $param) = @_; - + die "implement this in subclass"; #return ($cluster_conf, $fw_conf); @@ -470,7 +537,7 @@ sub save_config { sub rule_env { my ($class, $param) = @_; - + die "implement this in subclass"; } @@ -494,8 +561,8 @@ my $get_ipset_list = sub { my ($fw_conf) = @_; my $res = []; - foreach my $name (keys %{$fw_conf->{ipset}}) { - my $data = { + foreach my $name (sort keys %{$fw_conf->{ipset}}) { + my $data = { name => $name, }; if (my $comment = $fw_conf->{ipset_comments}->{$name}) { @@ -528,10 +595,10 @@ sub register_index { type => 'array', items => { type => "object", - properties => { + properties => { name => get_standard_option('ipset-name'), digest => get_standard_option('pve-config-digest', { optional => 0} ), - comment => { + comment => { type => 'string', optional => 1, } @@ -541,10 +608,10 @@ sub register_index { }, code => sub { my ($param) = @_; - + my ($cluster_conf, $fw_conf) = $class->load_config($param); - return &$get_ipset_list($fw_conf); + return &$get_ipset_list($fw_conf); }}); } @@ -577,38 +644,42 @@ sub register_create { returns => { type => 'null' }, code => sub { my ($param) = @_; - - my ($cluster_conf, $fw_conf) = $class->load_config($param); - if ($param->{rename}) { - my (undef, $digest) = &$get_ipset_list($fw_conf); - PVE::Tools::assert_if_modified($digest, $param->{digest}); + $class->lock_config($param, sub { + my ($param) = @_; - raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" }) - if !$fw_conf->{ipset}->{$param->{rename}}; + my ($cluster_conf, $fw_conf) = $class->load_config($param); - # prevent overwriting existing ipset - raise_param_exc({ name => "IPSet '$param->{name}' does already exist"}) - if $fw_conf->{ipset}->{$param->{name}} && - $param->{name} ne $param->{rename}; + if ($param->{rename}) { + my (undef, $digest) = &$get_ipset_list($fw_conf); + PVE::Tools::assert_if_modified($digest, $param->{digest}); - my $data = delete $fw_conf->{ipset}->{$param->{rename}}; - $fw_conf->{ipset}->{$param->{name}} = $data; - if (my $comment = delete $fw_conf->{ipset_comments}->{$param->{rename}}) { - $fw_conf->{ipset_comments}->{$param->{name}} = $comment; - } - $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment}); - } else { - foreach my $name (keys %{$fw_conf->{ipset}}) { - raise_param_exc({ name => "IPSet '$name' already exists" }) - if $name eq $param->{name}; - } + raise_param_exc({ name => "IPSet '$param->{rename}' does not exist" }) + if !$fw_conf->{ipset}->{$param->{rename}}; - $fw_conf->{ipset}->{$param->{name}} = []; - $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment}); - } + # prevent overwriting existing ipset + raise_param_exc({ name => "IPSet '$param->{name}' does already exist"}) + if $fw_conf->{ipset}->{$param->{name}} && + $param->{name} ne $param->{rename}; - $class->save_config($param, $fw_conf); + my $data = delete $fw_conf->{ipset}->{$param->{rename}}; + $fw_conf->{ipset}->{$param->{name}} = $data; + if (my $comment = delete $fw_conf->{ipset_comments}->{$param->{rename}}) { + $fw_conf->{ipset_comments}->{$param->{name}} = $comment; + } + $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment}); + } else { + foreach my $name (keys %{$fw_conf->{ipset}}) { + raise_param_exc({ name => "IPSet '$name' already exists" }) + if $name eq $param->{name}; + } + + $fw_conf->{ipset}->{$param->{name}} = []; + $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment}); + } + + $class->save_config($param, $fw_conf); + }); return undef; }}); @@ -631,13 +702,19 @@ use base qw(PVE::API2::Firewall::BaseIPSetList); sub rule_env { my ($class, $param) = @_; - + return 'cluster'; } +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_clusterfw_conf(10, $code, $param); +} + sub load_config { my ($class, $param) = @_; - + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); return (undef, $cluster_conf); } @@ -651,10 +728,10 @@ sub save_config { __PACKAGE__->register_handlers(); __PACKAGE__->register_method ({ - subclass => "PVE::API2::Firewall::ClusterIPset", + subclass => "PVE::API2::Firewall::ClusterIPset", path => '{name}', - # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' - fragmentDelimiter => '', + # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' + fragmentDelimiter => '', }); package PVE::API2::Firewall::VMIPSetList; @@ -666,20 +743,26 @@ use PVE::Firewall; use base qw(PVE::API2::Firewall::BaseIPSetList); -__PACKAGE__->additional_parameters({ +__PACKAGE__->additional_parameters({ node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid'), }); sub rule_env { my ($class, $param) = @_; - + return 'vm'; } +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param); +} + sub load_config { my ($class, $param) = @_; - + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'vm', $param->{vmid}); return ($cluster_conf, $fw_conf); @@ -694,10 +777,10 @@ sub save_config { __PACKAGE__->register_handlers(); __PACKAGE__->register_method ({ - subclass => "PVE::API2::Firewall::VMIPset", + subclass => "PVE::API2::Firewall::VMIPset", path => '{name}', - # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' - fragmentDelimiter => '', + # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' + fragmentDelimiter => '', }); package PVE::API2::Firewall::CTIPSetList; @@ -709,20 +792,26 @@ use PVE::Firewall; use base qw(PVE::API2::Firewall::BaseIPSetList); -__PACKAGE__->additional_parameters({ +__PACKAGE__->additional_parameters({ node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid'), }); sub rule_env { my ($class, $param) = @_; - + return 'ct'; } +sub lock_config { + my ($class, $param, $code) = @_; + + PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param); +} + sub load_config { my ($class, $param) = @_; - + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(); my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'ct', $param->{vmid}); return ($cluster_conf, $fw_conf); @@ -737,10 +826,10 @@ sub save_config { __PACKAGE__->register_handlers(); __PACKAGE__->register_method ({ - subclass => "PVE::API2::Firewall::CTIPset", + subclass => "PVE::API2::Firewall::CTIPset", path => '{name}', - # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' - fragmentDelimiter => '', + # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' + fragmentDelimiter => '', }); 1;