]>
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 = PVE
::Firewall
::clean_cidr
($param->{cidr
});
203 if ($cidr =~ m/^${PVE::Firewall::ip_alias_pattern}$/) {
204 # make sure alias exists (if $cidr is an alias)
205 PVE
::Firewall
::resolve_alias
($cluster_conf, $fw_conf, $cidr);
207 # normalize like config parser, otherwise duplicates might slip through
208 $cidr = PVE
::Firewall
::parse_ip_or_cidr
($cidr);
211 foreach my $entry (@$ipset) {
212 raise_param_exc
({ cidr
=> "address '$cidr' already exists" })
213 if $entry->{cidr
} eq $cidr;
216 raise_param_exc
({ cidr
=> "a zero prefix is not allowed in ipset entries" })
220 my $data = { cidr
=> $cidr };
222 $data->{nomatch
} = 1 if $param->{nomatch
};
223 $data->{comment
} = $param->{comment
} if $param->{comment
};
225 unshift @$ipset, $data;
227 $class->save_ipset($param, $fw_conf, $ipset);
235 sub register_read_ip
{
238 my $properties = $class->additional_parameters();
240 $properties->{name
} = $api_properties->{name
};
241 $properties->{cidr
} = $api_properties->{cidr
};
243 $class->register_method({
247 description
=> "Read IP or Network settings from IPSet.",
248 permissions
=> PVE
::Firewall
::rules_audit_permissions
($class->rule_env()),
251 additionalProperties
=> 0,
252 properties
=> $properties,
254 returns
=> { type
=> "object" },
258 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
260 my $list = PVE
::Firewall
::copy_list_with_digest
($ipset);
262 foreach my $entry (@$list) {
263 if ($entry->{cidr
} eq $param->{cidr
}) {
268 raise_param_exc
({ cidr
=> "no such IP/Network" });
272 sub register_update_ip
{
275 my $properties = $class->additional_parameters();
277 $properties->{name
} = $api_properties->{name
};
278 $properties->{cidr
} = $api_properties->{cidr
};
279 $properties->{nomatch
} = $api_properties->{nomatch
};
280 $properties->{comment
} = $api_properties->{comment
};
281 $properties->{digest
} = get_standard_option
('pve-config-digest');
283 $class->register_method({
287 description
=> "Update IP or Network settings",
289 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
291 additionalProperties
=> 0,
292 properties
=> $properties,
294 returns
=> { type
=> "null" },
298 my $found = $class->lock_config($param, sub {
301 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
303 my (undef, $digest) = PVE
::Firewall
::copy_list_with_digest
($ipset);
304 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
306 foreach my $entry (@$ipset) {
307 if($entry->{cidr
} eq $param->{cidr
}) {
308 $entry->{nomatch
} = $param->{nomatch
};
309 $entry->{comment
} = $param->{comment
};
310 $class->save_ipset($param, $fw_conf, $ipset);
320 raise_param_exc
({ cidr
=> "no such IP/Network" });
324 sub register_delete_ip
{
327 my $properties = $class->additional_parameters();
329 $properties->{name
} = $api_properties->{name
};
330 $properties->{cidr
} = $api_properties->{cidr
};
331 $properties->{digest
} = get_standard_option
('pve-config-digest');
333 $class->register_method({
337 description
=> "Remove IP or Network from IPSet.",
339 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
341 additionalProperties
=> 0,
342 properties
=> $properties,
344 returns
=> { type
=> "null" },
348 $class->lock_config($param, sub {
351 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
353 my (undef, $digest) = PVE
::Firewall
::copy_list_with_digest
($ipset);
354 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
358 foreach my $entry (@$ipset) {
359 push @$new, $entry if $entry->{cidr
} ne $param->{cidr
};
362 $class->save_ipset($param, $fw_conf, $new);
369 sub register_handlers
{
372 $class->register_delete_ipset();
373 $class->register_get_ipset();
374 $class->register_create_ip();
375 $class->register_read_ip();
376 $class->register_update_ip();
377 $class->register_delete_ip();
380 package PVE
::API2
::Firewall
::ClusterIPset
;
385 use base
qw(PVE::API2::Firewall::IPSetBase);
388 my ($class, $param) = @_;
394 my ($class, $param, $code) = @_;
396 PVE
::Firewall
::lock_clusterfw_conf
(10, $code, $param);
400 my ($class, $param) = @_;
402 my $fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
403 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
404 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
406 return (undef, $fw_conf, $ipset);
410 my ($class, $param, $fw_conf) = @_;
412 PVE
::Firewall
::save_clusterfw_conf
($fw_conf);
415 __PACKAGE__-
>register_handlers();
417 package PVE
::API2
::Firewall
::VMIPset
;
421 use PVE
::JSONSchema
qw(get_standard_option);
423 use base
qw(PVE::API2::Firewall::IPSetBase);
426 my ($class, $param) = @_;
431 __PACKAGE__-
>additional_parameters({
432 node
=> get_standard_option
('pve-node'),
433 vmid
=> get_standard_option
('pve-vmid'),
437 my ($class, $param, $code) = @_;
439 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
443 my ($class, $param) = @_;
445 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
446 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'vm', $param->{vmid
});
447 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
448 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
450 return ($cluster_conf, $fw_conf, $ipset);
454 my ($class, $param, $fw_conf) = @_;
456 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
459 __PACKAGE__-
>register_handlers();
461 package PVE
::API2
::Firewall
::CTIPset
;
465 use PVE
::JSONSchema
qw(get_standard_option);
467 use base
qw(PVE::API2::Firewall::IPSetBase);
470 my ($class, $param) = @_;
475 __PACKAGE__-
>additional_parameters({
476 node
=> get_standard_option
('pve-node'),
477 vmid
=> get_standard_option
('pve-vmid'),
481 my ($class, $param, $code) = @_;
483 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
487 my ($class, $param) = @_;
489 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
490 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'ct', $param->{vmid
});
491 my $ipset = $fw_conf->{ipset
}->{$param->{name
}};
492 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
494 return ($cluster_conf, $fw_conf, $ipset);
498 my ($class, $param, $fw_conf) = @_;
500 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
503 __PACKAGE__-
>register_handlers();
505 package PVE
::API2
::Firewall
::BaseIPSetList
;
509 use PVE
::JSONSchema
qw(get_standard_option);
510 use PVE
::Exception
qw(raise_param_exc);
513 use base
qw(PVE::RESTHandler);
516 my ($class, $param, $code) = @_;
518 die "implement this in subclass";
522 my ($class, $param) = @_;
524 die "implement this in subclass";
526 #return ($cluster_conf, $fw_conf);
530 my ($class, $param, $fw_conf) = @_;
532 die "implement this in subclass";
536 my ($class, $param) = @_;
538 die "implement this in subclass";
541 my $additional_param_hash_list = {};
543 sub additional_parameters
{
544 my ($class, $new_value) = @_;
546 if (defined($new_value)) {
547 $additional_param_hash_list->{$class} = $new_value;
552 my $org = $additional_param_hash_list->{$class} || {};
553 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
557 my $get_ipset_list = sub {
561 foreach my $name (sort keys %{$fw_conf->{ipset
}}) {
565 if (my $comment = $fw_conf->{ipset_comments
}->{$name}) {
566 $data->{comment
} = $comment;
571 my ($list, $digest) = PVE
::Firewall
::copy_list_with_digest
($res);
573 return wantarray ?
($list, $digest) : $list;
579 my $properties = $class->additional_parameters();
581 $class->register_method({
582 name
=> 'ipset_index',
585 description
=> "List IPSets",
586 permissions
=> PVE
::Firewall
::rules_audit_permissions
($class->rule_env()),
588 additionalProperties
=> 0,
589 properties
=> $properties,
596 name
=> get_standard_option
('ipset-name'),
597 digest
=> get_standard_option
('pve-config-digest', { optional
=> 0} ),
604 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
609 my ($cluster_conf, $fw_conf) = $class->load_config($param);
611 return &$get_ipset_list($fw_conf);
615 sub register_create
{
618 my $properties = $class->additional_parameters();
620 $properties->{name
} = get_standard_option
('ipset-name');
622 $properties->{comment
} = { type
=> 'string', optional
=> 1 };
624 $properties->{digest
} = get_standard_option
('pve-config-digest');
626 $properties->{rename} = get_standard_option
('ipset-name', {
627 description
=> "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
630 $class->register_method({
631 name
=> 'create_ipset',
634 description
=> "Create new IPSet",
636 permissions
=> PVE
::Firewall
::rules_modify_permissions
($class->rule_env()),
638 additionalProperties
=> 0,
639 properties
=> $properties,
641 returns
=> { type
=> 'null' },
645 $class->lock_config($param, sub {
648 my ($cluster_conf, $fw_conf) = $class->load_config($param);
650 if ($param->{rename}) {
651 my (undef, $digest) = &$get_ipset_list($fw_conf);
652 PVE
::Tools
::assert_if_modified
($digest, $param->{digest
});
654 raise_param_exc
({ name
=> "IPSet '$param->{rename}' does not exist" })
655 if !$fw_conf->{ipset
}->{$param->{rename}};
657 # prevent overwriting existing ipset
658 raise_param_exc
({ name
=> "IPSet '$param->{name}' does already exist"})
659 if $fw_conf->{ipset
}->{$param->{name
}} &&
660 $param->{name
} ne $param->{rename};
662 my $data = delete $fw_conf->{ipset
}->{$param->{rename}};
663 $fw_conf->{ipset
}->{$param->{name
}} = $data;
664 if (my $comment = delete $fw_conf->{ipset_comments
}->{$param->{rename}}) {
665 $fw_conf->{ipset_comments
}->{$param->{name
}} = $comment;
667 $fw_conf->{ipset_comments
}->{$param->{name
}} = $param->{comment
} if defined($param->{comment
});
669 foreach my $name (keys %{$fw_conf->{ipset
}}) {
670 raise_param_exc
({ name
=> "IPSet '$name' already exists" })
671 if $name eq $param->{name
};
674 $fw_conf->{ipset
}->{$param->{name
}} = [];
675 $fw_conf->{ipset_comments
}->{$param->{name
}} = $param->{comment
} if defined($param->{comment
});
678 $class->save_config($param, $fw_conf);
685 sub register_handlers
{
688 $class->register_index();
689 $class->register_create();
692 package PVE
::API2
::Firewall
::ClusterIPSetList
;
698 use base
qw(PVE::API2::Firewall::BaseIPSetList);
701 my ($class, $param) = @_;
707 my ($class, $param, $code) = @_;
709 PVE
::Firewall
::lock_clusterfw_conf
(10, $code, $param);
713 my ($class, $param) = @_;
715 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
716 return (undef, $cluster_conf);
720 my ($class, $param, $fw_conf) = @_;
722 PVE
::Firewall
::save_clusterfw_conf
($fw_conf);
725 __PACKAGE__-
>register_handlers();
727 __PACKAGE__-
>register_method ({
728 subclass
=> "PVE::API2::Firewall::ClusterIPset",
730 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
731 fragmentDelimiter
=> '',
734 package PVE
::API2
::Firewall
::VMIPSetList
;
738 use PVE
::JSONSchema
qw(get_standard_option);
741 use base
qw(PVE::API2::Firewall::BaseIPSetList);
743 __PACKAGE__-
>additional_parameters({
744 node
=> get_standard_option
('pve-node'),
745 vmid
=> get_standard_option
('pve-vmid'),
749 my ($class, $param) = @_;
755 my ($class, $param, $code) = @_;
757 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
761 my ($class, $param) = @_;
763 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
764 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'vm', $param->{vmid
});
765 return ($cluster_conf, $fw_conf);
769 my ($class, $param, $fw_conf) = @_;
771 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
774 __PACKAGE__-
>register_handlers();
776 __PACKAGE__-
>register_method ({
777 subclass
=> "PVE::API2::Firewall::VMIPset",
779 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
780 fragmentDelimiter
=> '',
783 package PVE
::API2
::Firewall
::CTIPSetList
;
787 use PVE
::JSONSchema
qw(get_standard_option);
790 use base
qw(PVE::API2::Firewall::BaseIPSetList);
792 __PACKAGE__-
>additional_parameters({
793 node
=> get_standard_option
('pve-node'),
794 vmid
=> get_standard_option
('pve-vmid'),
798 my ($class, $param) = @_;
804 my ($class, $param, $code) = @_;
806 PVE
::Firewall
::lock_vmfw_conf
($param->{vmid
}, 10, $code, $param);
810 my ($class, $param) = @_;
812 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
813 my $fw_conf = PVE
::Firewall
::load_vmfw_conf
($cluster_conf, 'ct', $param->{vmid
});
814 return ($cluster_conf, $fw_conf);
818 my ($class, $param, $fw_conf) = @_;
820 PVE
::Firewall
::save_vmfw_conf
($param->{vmid
}, $fw_conf);
823 __PACKAGE__-
>register_handlers();
825 __PACKAGE__-
>register_method ({
826 subclass
=> "PVE::API2::Firewall::CTIPset",
828 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
829 fragmentDelimiter
=> '',