]>
git.proxmox.com Git - pve-firewall.git/blob - src/PVE/API2/Firewall/IPSet.pm
1 package PVE
::API2
::Firewall
::IPSetBase
;
5 use PVE
::Exception
qw(raise raise_param_exc);
6 use PVE
::JSONSchema
qw(get_standard_option);
10 use base
qw(PVE::RESTHandler);
12 my $api_properties = {
14 description
=> "Network/IP specification in CIDR format.",
15 type
=> 'string', format
=> 'IPorCIDRorAlias',
17 name
=> get_standard_option
('ipset-name'),
29 my ($class, $param, $code) = @_;
31 die "implement this in subclass";
35 my ($class, $param) = @_;
37 die "implement this in subclass";
39 #return ($cluster_conf, $fw_conf, $ipset);
43 my ($class, $param, $fw_conf) = @_;
45 die "implement this in subclass";
49 my ($class, $param) = @_;
51 die "implement this in subclass";
55 my ($class, $param, $fw_conf, $ipset) = @_;
57 if (!defined($ipset)) {
58 delete $fw_conf->{ipset
}->{$param->{name
}};
60 $fw_conf->{ipset
}->{$param->{name
}} = $ipset;
63 $class->save_config($param, $fw_conf);
66 my $additional_param_hash = {};
68 sub additional_parameters
{
69 my ($class, $new_value) = @_;
71 if (defined($new_value)) {
72 $additional_param_hash->{$class} = $new_value;
77 my $org = $additional_param_hash->{$class} || {};
78 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
82 sub register_get_ipset
{
85 my $properties = $class->additional_parameters();
87 $properties->{name
} = $api_properties->{name
};
89 $class->register_method({
93 description
=> "List IPSet content",
94 permissions
=> PVE
::Firewall
::rules_audit_permissions
($class->rule_env()),
96 additionalProperties
=> 0,
97 properties
=> $properties,
115 digest
=> get_standard_option
('pve-config-digest', { optional
=> 0} ),
118 links
=> [ { rel
=> 'child', href
=> "{cidr}" } ],
123 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
125 return PVE
::Firewall
::copy_list_with_digest
($ipset);
129 sub register_delete_ipset
{
132 my $properties = $class->additional_parameters();
134 $properties->{name
} = get_standard_option
('ipset-name');
135 $properties->{force
} = {
138 description
=> 'Delete all members of the IPSet, if there are any.',
141 $class->register_method({
142 name
=> 'delete_ipset',
145 description
=> "Delete IPSet",
147 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
149 additionalProperties
=> 0,
150 properties
=> $properties,
152 returns
=> { type
=> 'null' },
156 $class->lock_config($param, sub {
159 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
161 die "IPSet '$param->{name}' is not empty\n"
162 if scalar(@$ipset) && !$param->{force
};
164 $class->save_ipset($param, $fw_conf, undef);
172 sub register_create_ip
{
175 my $properties = $class->additional_parameters();
177 $properties->{name
} = $api_properties->{name
};
178 $properties->{cidr
} = $api_properties->{cidr
};
179 $properties->{nomatch
} = $api_properties->{nomatch
};
180 $properties->{comment
} = $api_properties->{comment
};
182 $class->register_method({
186 description
=> "Add IP or Network to IPSet.",
188 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
190 additionalProperties
=> 0,
191 properties
=> $properties,
193 returns
=> { type
=> "null" },
197 $class->lock_config($param, sub {
200 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
202 my $cidr = $param->{cidr
};
203 if ($cidr =~ m
@^(dc
/|guest/)?
(${PVE
::Firewall
::ip_alias_pattern
})$@) {
204 my $scope = $1 // "";
206 # make sure alias exists (if $cidr is an alias)
207 PVE
::Firewall
::resolve_alias
($cluster_conf, $fw_conf, $alias, $scope);
209 $cidr = PVE
::Firewall
::clean_cidr
($cidr);
210 # normalize like config parser, otherwise duplicates might slip through
211 $cidr = PVE
::Firewall
::parse_ip_or_cidr
($cidr);
214 foreach my $entry (@$ipset) {
215 raise_param_exc
({ cidr
=> "address '$cidr' already exists" })
216 if $entry->{cidr
} eq $cidr;
219 raise_param_exc
({ cidr
=> "a zero prefix is not allowed in ipset entries" })
223 my $data = { cidr
=> $cidr };
225 $data->{nomatch
} = 1 if $param->{nomatch
};
226 $data->{comment
} = $param->{comment
} if $param->{comment
};
228 unshift @$ipset, $data;
230 $class->save_ipset($param, $fw_conf, $ipset);
238 sub register_read_ip
{
241 my $properties = $class->additional_parameters();
243 $properties->{name
} = $api_properties->{name
};
244 $properties->{cidr
} = $api_properties->{cidr
};
246 $class->register_method({
250 description
=> "Read IP or Network settings from IPSet.",
251 permissions
=> PVE
::Firewall
::rules_audit_permissions
($class->rule_env()),
254 additionalProperties
=> 0,
255 properties
=> $properties,
257 returns
=> { type
=> "object" },
261 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
263 my $list = PVE
::Firewall
::copy_list_with_digest
($ipset);
265 foreach my $entry (@$list) {
266 if ($entry->{cidr
} eq $param->{cidr
}) {
271 raise_param_exc
({ cidr
=> "no such IP/Network" });
275 sub register_update_ip
{
278 my $properties = $class->additional_parameters();
280 $properties->{name
} = $api_properties->{name
};
281 $properties->{cidr
} = $api_properties->{cidr
};
282 $properties->{nomatch
} = $api_properties->{nomatch
};
283 $properties->{comment
} = $api_properties->{comment
};
284 $properties->{digest
} = get_standard_option
('pve-config-digest');
286 $class->register_method({
290 description
=> "Update IP or Network settings",
292 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
294 additionalProperties
=> 0,
295 properties
=> $properties,
297 returns
=> { type
=> "null" },
301 my $found = $class->lock_config($param, sub {
304 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
306 my (undef, $digest) = PVE
::Firewall
::copy_list_with_digest
($ipset);
307 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
309 foreach my $entry (@$ipset) {
310 if($entry->{cidr
} eq $param->{cidr
}) {
311 $entry->{nomatch
} = $param->{nomatch
};
312 $entry->{comment
} = $param->{comment
};
313 $class->save_ipset($param, $fw_conf, $ipset);
323 raise_param_exc
({ cidr
=> "no such IP/Network" });
327 sub register_delete_ip
{
330 my $properties = $class->additional_parameters();
332 $properties->{name
} = $api_properties->{name
};
333 $properties->{cidr
} = $api_properties->{cidr
};
334 $properties->{digest
} = get_standard_option
('pve-config-digest');
336 $class->register_method({
340 description
=> "Remove IP or Network from IPSet.",
342 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
344 additionalProperties
=> 0,
345 properties
=> $properties,
347 returns
=> { type
=> "null" },
351 $class->lock_config($param, sub {
354 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
356 my (undef, $digest) = PVE
::Firewall
::copy_list_with_digest
($ipset);
357 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
361 foreach my $entry (@$ipset) {
362 push @$new, $entry if $entry->{cidr
} ne $param->{cidr
};
365 $class->save_ipset($param, $fw_conf, $new);
372 sub register_handlers
{
375 $class->register_delete_ipset();
376 $class->register_get_ipset();
377 $class->register_create_ip();
378 $class->register_read_ip();
379 $class->register_update_ip();
380 $class->register_delete_ip();
383 package PVE
::API2
::Firewall
::ClusterIPset
;
388 use base
qw(PVE::API2::Firewall::IPSetBase);
391 my ($class, $param) = @_;
397 my ($class, $param, $code) = @_;
399 PVE
::Firewall
::lock_clusterfw_conf
(10, $code, $param);
403 my ($class, $param) = @_;
405 my $fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
406 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
407 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
409 return (undef, $fw_conf, $ipset);
413 my ($class, $param, $fw_conf) = @_;
415 PVE
::Firewall
::save_clusterfw_conf
($fw_conf);
418 __PACKAGE__-
>register_handlers();
420 package PVE
::API2
::Firewall
::VMIPset
;
424 use PVE
::JSONSchema
qw(get_standard_option);
426 use base
qw(PVE::API2::Firewall::IPSetBase);
429 my ($class, $param) = @_;
434 __PACKAGE__-
>additional_parameters({
435 node
=> get_standard_option
('pve-node'),
436 vmid
=> get_standard_option
('pve-vmid'),
440 my ($class, $param, $code) = @_;
442 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
446 my ($class, $param) = @_;
448 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
449 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'vm', $param->{vmid
});
450 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
451 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
453 return ($cluster_conf, $fw_conf, $ipset);
457 my ($class, $param, $fw_conf) = @_;
459 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
462 __PACKAGE__-
>register_handlers();
464 package PVE
::API2
::Firewall
::CTIPset
;
468 use PVE
::JSONSchema
qw(get_standard_option);
470 use base
qw(PVE::API2::Firewall::IPSetBase);
473 my ($class, $param) = @_;
478 __PACKAGE__-
>additional_parameters({
479 node
=> get_standard_option
('pve-node'),
480 vmid
=> get_standard_option
('pve-vmid'),
484 my ($class, $param, $code) = @_;
486 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
490 my ($class, $param) = @_;
492 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
493 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'ct', $param->{vmid
});
494 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
495 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
497 return ($cluster_conf, $fw_conf, $ipset);
501 my ($class, $param, $fw_conf) = @_;
503 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
506 __PACKAGE__-
>register_handlers();
508 package PVE
::API2
::Firewall
::BaseIPSetList
;
512 use PVE
::JSONSchema
qw(get_standard_option);
513 use PVE
::Exception
qw(raise_param_exc);
516 use base
qw(PVE::RESTHandler);
519 my ($class, $param, $code) = @_;
521 die "implement this in subclass";
525 my ($class, $param) = @_;
527 die "implement this in subclass";
529 #return ($cluster_conf, $fw_conf);
533 my ($class, $param, $fw_conf) = @_;
535 die "implement this in subclass";
539 my ($class, $param) = @_;
541 die "implement this in subclass";
544 my $additional_param_hash_list = {};
546 sub additional_parameters
{
547 my ($class, $new_value) = @_;
549 if (defined($new_value)) {
550 $additional_param_hash_list->{$class} = $new_value;
555 my $org = $additional_param_hash_list->{$class} || {};
556 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
560 my $get_ipset_list = sub {
564 foreach my $name (sort keys %{$fw_conf->{ipset
}}) {
568 if (my $comment = $fw_conf->{ipset_comments
}->{$name}) {
569 $data->{comment
} = $comment;
574 my ($list, $digest) = PVE
::Firewall
::copy_list_with_digest
($res);
576 return wantarray ?
($list, $digest) : $list;
582 my $properties = $class->additional_parameters();
584 $class->register_method({
585 name
=> 'ipset_index',
588 description
=> "List IPSets",
589 permissions
=> PVE
::Firewall
::rules_audit_permissions
($class->rule_env()),
591 additionalProperties
=> 0,
592 properties
=> $properties,
599 name
=> get_standard_option
('ipset-name'),
600 digest
=> get_standard_option
('pve-config-digest', { optional
=> 0} ),
607 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
612 my ($cluster_conf, $fw_conf) = $class->load_config($param);
614 return &$get_ipset_list($fw_conf);
618 sub register_create
{
621 my $properties = $class->additional_parameters();
623 $properties->{name
} = get_standard_option
('ipset-name');
625 $properties->{comment
} = { type
=> 'string', optional
=> 1 };
627 $properties->{digest
} = get_standard_option
('pve-config-digest');
629 $properties->{rename} = get_standard_option
('ipset-name', {
630 description
=> "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
633 $class->register_method({
634 name
=> 'create_ipset',
637 description
=> "Create new IPSet",
639 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
641 additionalProperties
=> 0,
642 properties
=> $properties,
644 returns
=> { type
=> 'null' },
648 $class->lock_config($param, sub {
651 my ($cluster_conf, $fw_conf) = $class->load_config($param);
653 if ($param->{rename}) {
654 my (undef, $digest) = &$get_ipset_list($fw_conf);
655 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
657 raise_param_exc
({ name
=> "IPSet '$param->{rename}' does not exist" })
658 if !$fw_conf->{ipset
}->{$param->{rename}};
660 # prevent overwriting existing ipset
661 raise_param_exc
({ name
=> "IPSet '$param->{name}' does already exist"})
662 if $fw_conf->{ipset
}->{$param->{name
}} &&
663 $param->{name
} ne $param->{rename};
665 my $data = delete $fw_conf->{ipset
}->{$param->{rename}};
666 $fw_conf->{ipset
}->{$param->{name
}} = $data;
667 if (my $comment = delete $fw_conf->{ipset_comments
}->{$param->{rename}}) {
668 $fw_conf->{ipset_comments
}->{$param->{name
}} = $comment;
670 $fw_conf->{ipset_comments
}->{$param->{name
}} = $param->{comment
} if defined($param->{comment
});
672 foreach my $name (keys %{$fw_conf->{ipset
}}) {
673 raise_param_exc
({ name
=> "IPSet '$name' already exists" })
674 if $name eq $param->{name
};
677 $fw_conf->{ipset
}->{$param->{name
}} = [];
678 $fw_conf->{ipset_comments
}->{$param->{name
}} = $param->{comment
} if defined($param->{comment
});
681 $class->save_config($param, $fw_conf);
688 sub register_handlers
{
691 $class->register_index();
692 $class->register_create();
695 package PVE
::API2
::Firewall
::ClusterIPSetList
;
701 use base
qw(PVE::API2::Firewall::BaseIPSetList);
704 my ($class, $param) = @_;
710 my ($class, $param, $code) = @_;
712 PVE
::Firewall
::lock_clusterfw_conf
(10, $code, $param);
716 my ($class, $param) = @_;
718 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
719 return (undef, $cluster_conf);
723 my ($class, $param, $fw_conf) = @_;
725 PVE
::Firewall
::save_clusterfw_conf
($fw_conf);
728 __PACKAGE__-
>register_handlers();
730 __PACKAGE__-
>register_method ({
731 subclass
=> "PVE::API2::Firewall::ClusterIPset",
733 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
734 fragmentDelimiter
=> '',
737 package PVE
::API2
::Firewall
::VMIPSetList
;
741 use PVE
::JSONSchema
qw(get_standard_option);
744 use base
qw(PVE::API2::Firewall::BaseIPSetList);
746 __PACKAGE__-
>additional_parameters({
747 node
=> get_standard_option
('pve-node'),
748 vmid
=> get_standard_option
('pve-vmid'),
752 my ($class, $param) = @_;
758 my ($class, $param, $code) = @_;
760 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
764 my ($class, $param) = @_;
766 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
767 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'vm', $param->{vmid
});
768 return ($cluster_conf, $fw_conf);
772 my ($class, $param, $fw_conf) = @_;
774 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
777 __PACKAGE__-
>register_handlers();
779 __PACKAGE__-
>register_method ({
780 subclass
=> "PVE::API2::Firewall::VMIPset",
782 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
783 fragmentDelimiter
=> '',
786 package PVE
::API2
::Firewall
::CTIPSetList
;
790 use PVE
::JSONSchema
qw(get_standard_option);
793 use base
qw(PVE::API2::Firewall::BaseIPSetList);
795 __PACKAGE__-
>additional_parameters({
796 node
=> get_standard_option
('pve-node'),
797 vmid
=> get_standard_option
('pve-vmid'),
801 my ($class, $param) = @_;
807 my ($class, $param, $code) = @_;
809 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
813 my ($class, $param) = @_;
815 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
816 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'ct', $param->{vmid
});
817 return ($cluster_conf, $fw_conf);
821 my ($class, $param, $fw_conf) = @_;
823 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
826 __PACKAGE__-
>register_handlers();
828 __PACKAGE__-
>register_method ({
829 subclass
=> "PVE::API2::Firewall::CTIPset",
831 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
832 fragmentDelimiter
=> '',