]>
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');
136 $class->register_method({
137 name
=> 'delete_ipset',
140 description
=> "Delete IPSet",
142 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
144 additionalProperties
=> 0,
145 properties
=> $properties,
147 returns
=> { type
=> 'null' },
151 $class->lock_config($param, sub {
154 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
156 die "IPSet '$param->{name}' is not empty\n"
159 $class->save_ipset($param, $fw_conf, undef);
167 sub register_create_ip
{
170 my $properties = $class->additional_parameters();
172 $properties->{name
} = $api_properties->{name
};
173 $properties->{cidr
} = $api_properties->{cidr
};
174 $properties->{nomatch
} = $api_properties->{nomatch
};
175 $properties->{comment
} = $api_properties->{comment
};
177 $class->register_method({
181 description
=> "Add IP or Network to IPSet.",
183 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
185 additionalProperties
=> 0,
186 properties
=> $properties,
188 returns
=> { type
=> "null" },
192 $class->lock_config($param, sub {
195 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
197 my $cidr = $param->{cidr
};
198 if ($cidr =~ m/^${PVE::Firewall::ip_alias_pattern}$/) {
199 # make sure alias exists (if $cidr is an alias)
200 PVE
::Firewall
::resolve_alias
($cluster_conf, $fw_conf, $cidr);
202 # normalize like config parser, otherwise duplicates might slip through
203 $cidr = PVE
::Firewall
::parse_ip_or_cidr
($cidr);
206 foreach my $entry (@$ipset) {
207 raise_param_exc
({ cidr
=> "address '$cidr' already exists" })
208 if $entry->{cidr
} eq $cidr;
211 raise_param_exc
({ cidr
=> "a zero prefix is not allowed in ipset entries" })
215 my $data = { cidr
=> $cidr };
217 $data->{nomatch
} = 1 if $param->{nomatch
};
218 $data->{comment
} = $param->{comment
} if $param->{comment
};
220 unshift @$ipset, $data;
222 $class->save_ipset($param, $fw_conf, $ipset);
230 sub register_read_ip
{
233 my $properties = $class->additional_parameters();
235 $properties->{name
} = $api_properties->{name
};
236 $properties->{cidr
} = $api_properties->{cidr
};
238 $class->register_method({
242 description
=> "Read IP or Network settings from IPSet.",
243 permissions
=> PVE
::Firewall
::rules_audit_permissions
($class->rule_env()),
246 additionalProperties
=> 0,
247 properties
=> $properties,
249 returns
=> { type
=> "object" },
253 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
255 my $list = PVE
::Firewall
::copy_list_with_digest
($ipset);
257 foreach my $entry (@$list) {
258 if ($entry->{cidr
} eq $param->{cidr
}) {
263 raise_param_exc
({ cidr
=> "no such IP/Network" });
267 sub register_update_ip
{
270 my $properties = $class->additional_parameters();
272 $properties->{name
} = $api_properties->{name
};
273 $properties->{cidr
} = $api_properties->{cidr
};
274 $properties->{nomatch
} = $api_properties->{nomatch
};
275 $properties->{comment
} = $api_properties->{comment
};
276 $properties->{digest
} = get_standard_option
('pve-config-digest');
278 $class->register_method({
282 description
=> "Update IP or Network settings",
284 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
286 additionalProperties
=> 0,
287 properties
=> $properties,
289 returns
=> { type
=> "null" },
293 my $found = $class->lock_config($param, sub {
296 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
298 my (undef, $digest) = PVE
::Firewall
::copy_list_with_digest
($ipset);
299 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
301 foreach my $entry (@$ipset) {
302 if($entry->{cidr
} eq $param->{cidr
}) {
303 $entry->{nomatch
} = $param->{nomatch
};
304 $entry->{comment
} = $param->{comment
};
305 $class->save_ipset($param, $fw_conf, $ipset);
315 raise_param_exc
({ cidr
=> "no such IP/Network" });
319 sub register_delete_ip
{
322 my $properties = $class->additional_parameters();
324 $properties->{name
} = $api_properties->{name
};
325 $properties->{cidr
} = $api_properties->{cidr
};
326 $properties->{digest
} = get_standard_option
('pve-config-digest');
328 $class->register_method({
332 description
=> "Remove IP or Network from IPSet.",
334 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
336 additionalProperties
=> 0,
337 properties
=> $properties,
339 returns
=> { type
=> "null" },
343 $class->lock_config($param, sub {
346 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
348 my (undef, $digest) = PVE
::Firewall
::copy_list_with_digest
($ipset);
349 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
353 foreach my $entry (@$ipset) {
354 push @$new, $entry if $entry->{cidr
} ne $param->{cidr
};
357 $class->save_ipset($param, $fw_conf, $new);
364 sub register_handlers
{
367 $class->register_delete_ipset();
368 $class->register_get_ipset();
369 $class->register_create_ip();
370 $class->register_read_ip();
371 $class->register_update_ip();
372 $class->register_delete_ip();
375 package PVE
::API2
::Firewall
::ClusterIPset
;
380 use base
qw(PVE::API2::Firewall::IPSetBase);
383 my ($class, $param) = @_;
389 my ($class, $param, $code) = @_;
391 PVE
::Firewall
::lock_clusterfw_conf
(10, $code, $param);
395 my ($class, $param) = @_;
397 my $fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
398 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
399 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
401 return (undef, $fw_conf, $ipset);
405 my ($class, $param, $fw_conf) = @_;
407 PVE
::Firewall
::save_clusterfw_conf
($fw_conf);
410 __PACKAGE__-
>register_handlers();
412 package PVE
::API2
::Firewall
::VMIPset
;
416 use PVE
::JSONSchema
qw(get_standard_option);
418 use base
qw(PVE::API2::Firewall::IPSetBase);
421 my ($class, $param) = @_;
426 __PACKAGE__-
>additional_parameters({
427 node
=> get_standard_option
('pve-node'),
428 vmid
=> get_standard_option
('pve-vmid'),
432 my ($class, $param, $code) = @_;
434 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
438 my ($class, $param) = @_;
440 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
441 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'vm', $param->{vmid
});
442 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
443 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
445 return ($cluster_conf, $fw_conf, $ipset);
449 my ($class, $param, $fw_conf) = @_;
451 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
454 __PACKAGE__-
>register_handlers();
456 package PVE
::API2
::Firewall
::CTIPset
;
460 use PVE
::JSONSchema
qw(get_standard_option);
462 use base
qw(PVE::API2::Firewall::IPSetBase);
465 my ($class, $param) = @_;
470 __PACKAGE__-
>additional_parameters({
471 node
=> get_standard_option
('pve-node'),
472 vmid
=> get_standard_option
('pve-vmid'),
476 my ($class, $param, $code) = @_;
478 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
482 my ($class, $param) = @_;
484 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
485 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'ct', $param->{vmid
});
486 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
487 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
489 return ($cluster_conf, $fw_conf, $ipset);
493 my ($class, $param, $fw_conf) = @_;
495 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
498 __PACKAGE__-
>register_handlers();
500 package PVE
::API2
::Firewall
::BaseIPSetList
;
504 use PVE
::JSONSchema
qw(get_standard_option);
505 use PVE
::Exception
qw(raise_param_exc);
508 use base
qw(PVE::RESTHandler);
511 my ($class, $param, $code) = @_;
513 die "implement this in subclass";
517 my ($class, $param) = @_;
519 die "implement this in subclass";
521 #return ($cluster_conf, $fw_conf);
525 my ($class, $param, $fw_conf) = @_;
527 die "implement this in subclass";
531 my ($class, $param) = @_;
533 die "implement this in subclass";
536 my $additional_param_hash_list = {};
538 sub additional_parameters
{
539 my ($class, $new_value) = @_;
541 if (defined($new_value)) {
542 $additional_param_hash_list->{$class} = $new_value;
547 my $org = $additional_param_hash_list->{$class} || {};
548 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
552 my $get_ipset_list = sub {
556 foreach my $name (sort keys %{$fw_conf->{ipset
}}) {
560 if (my $comment = $fw_conf->{ipset_comments
}->{$name}) {
561 $data->{comment
} = $comment;
566 my ($list, $digest) = PVE
::Firewall
::copy_list_with_digest
($res);
568 return wantarray ?
($list, $digest) : $list;
574 my $properties = $class->additional_parameters();
576 $class->register_method({
577 name
=> 'ipset_index',
580 description
=> "List IPSets",
581 permissions
=> PVE
::Firewall
::rules_audit_permissions
($class->rule_env()),
583 additionalProperties
=> 0,
584 properties
=> $properties,
591 name
=> get_standard_option
('ipset-name'),
592 digest
=> get_standard_option
('pve-config-digest', { optional
=> 0} ),
599 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
604 my ($cluster_conf, $fw_conf) = $class->load_config($param);
606 return &$get_ipset_list($fw_conf);
610 sub register_create
{
613 my $properties = $class->additional_parameters();
615 $properties->{name
} = get_standard_option
('ipset-name');
617 $properties->{comment
} = { type
=> 'string', optional
=> 1 };
619 $properties->{digest
} = get_standard_option
('pve-config-digest');
621 $properties->{rename} = get_standard_option
('ipset-name', {
622 description
=> "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
625 $class->register_method({
626 name
=> 'create_ipset',
629 description
=> "Create new IPSet",
631 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
633 additionalProperties
=> 0,
634 properties
=> $properties,
636 returns
=> { type
=> 'null' },
640 $class->lock_config($param, sub {
643 my ($cluster_conf, $fw_conf) = $class->load_config($param);
645 if ($param->{rename}) {
646 my (undef, $digest) = &$get_ipset_list($fw_conf);
647 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
649 raise_param_exc
({ name
=> "IPSet '$param->{rename}' does not exist" })
650 if !$fw_conf->{ipset
}->{$param->{rename}};
652 # prevent overwriting existing ipset
653 raise_param_exc
({ name
=> "IPSet '$param->{name}' does already exist"})
654 if $fw_conf->{ipset
}->{$param->{name
}} &&
655 $param->{name
} ne $param->{rename};
657 my $data = delete $fw_conf->{ipset
}->{$param->{rename}};
658 $fw_conf->{ipset
}->{$param->{name
}} = $data;
659 if (my $comment = delete $fw_conf->{ipset_comments
}->{$param->{rename}}) {
660 $fw_conf->{ipset_comments
}->{$param->{name
}} = $comment;
662 $fw_conf->{ipset_comments
}->{$param->{name
}} = $param->{comment
} if defined($param->{comment
});
664 foreach my $name (keys %{$fw_conf->{ipset
}}) {
665 raise_param_exc
({ name
=> "IPSet '$name' already exists" })
666 if $name eq $param->{name
};
669 $fw_conf->{ipset
}->{$param->{name
}} = [];
670 $fw_conf->{ipset_comments
}->{$param->{name
}} = $param->{comment
} if defined($param->{comment
});
673 $class->save_config($param, $fw_conf);
680 sub register_handlers
{
683 $class->register_index();
684 $class->register_create();
687 package PVE
::API2
::Firewall
::ClusterIPSetList
;
693 use base
qw(PVE::API2::Firewall::BaseIPSetList);
696 my ($class, $param) = @_;
702 my ($class, $param, $code) = @_;
704 PVE
::Firewall
::lock_clusterfw_conf
(10, $code, $param);
708 my ($class, $param) = @_;
710 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
711 return (undef, $cluster_conf);
715 my ($class, $param, $fw_conf) = @_;
717 PVE
::Firewall
::save_clusterfw_conf
($fw_conf);
720 __PACKAGE__-
>register_handlers();
722 __PACKAGE__-
>register_method ({
723 subclass
=> "PVE::API2::Firewall::ClusterIPset",
725 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
726 fragmentDelimiter
=> '',
729 package PVE
::API2
::Firewall
::VMIPSetList
;
733 use PVE
::JSONSchema
qw(get_standard_option);
736 use base
qw(PVE::API2::Firewall::BaseIPSetList);
738 __PACKAGE__-
>additional_parameters({
739 node
=> get_standard_option
('pve-node'),
740 vmid
=> get_standard_option
('pve-vmid'),
744 my ($class, $param) = @_;
750 my ($class, $param, $code) = @_;
752 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
756 my ($class, $param) = @_;
758 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
759 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'vm', $param->{vmid
});
760 return ($cluster_conf, $fw_conf);
764 my ($class, $param, $fw_conf) = @_;
766 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
769 __PACKAGE__-
>register_handlers();
771 __PACKAGE__-
>register_method ({
772 subclass
=> "PVE::API2::Firewall::VMIPset",
774 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
775 fragmentDelimiter
=> '',
778 package PVE
::API2
::Firewall
::CTIPSetList
;
782 use PVE
::JSONSchema
qw(get_standard_option);
785 use base
qw(PVE::API2::Firewall::BaseIPSetList);
787 __PACKAGE__-
>additional_parameters({
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid'),
793 my ($class, $param) = @_;
799 my ($class, $param, $code) = @_;
801 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
805 my ($class, $param) = @_;
807 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
808 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'ct', $param->{vmid
});
809 return ($cluster_conf, $fw_conf);
813 my ($class, $param, $fw_conf) = @_;
815 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
818 __PACKAGE__-
>register_handlers();
820 __PACKAGE__-
>register_method ({
821 subclass
=> "PVE::API2::Firewall::CTIPset",
823 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
824 fragmentDelimiter
=> '',