]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/API2/Firewall/IPSet.pm
another regression test
[pve-firewall.git] / src / PVE / API2 / Firewall / IPSet.pm
CommitLineData
009ee3ac
DM
1package PVE::API2::Firewall::IPSetBase;
2
3use strict;
4use warnings;
4a11bba5 5use PVE::Exception qw(raise raise_param_exc);
009ee3ac
DM
6use PVE::JSONSchema qw(get_standard_option);
7
8use PVE::Firewall;
9
10use base qw(PVE::RESTHandler);
11
12my $api_properties = {
13 cidr => {
14 description => "Network/IP specification in CIDR format.",
7c619bbb 15 type => 'string', format => 'IPv4orCIDRorAlias',
009ee3ac 16 },
e74a87f5 17 name => get_standard_option('ipset-name'),
009ee3ac
DM
18 comment => {
19 type => 'string',
20 optional => 1,
21 },
22 nomatch => {
23 type => 'boolean',
24 optional => 1,
25 },
26};
27
28sub load_config {
29 my ($class, $param) = @_;
30
31 die "implement this in subclass";
1210ae94
DM
32
33 #return ($cluster_conf, $fw_conf, $ipset);
009ee3ac
DM
34}
35
1210ae94
DM
36sub save_config {
37 my ($class, $param, $fw_conf) = @_;
009ee3ac
DM
38
39 die "implement this in subclass";
40}
41
1210ae94
DM
42sub save_ipset {
43 my ($class, $param, $fw_conf, $ipset) = @_;
44
45 if (!defined($ipset)) {
46 delete $fw_conf->{ipset}->{$param->{name}};
47 } else {
48 $fw_conf->{ipset}->{$param->{name}} = $ipset;
49 }
50
51 $class->save_config($param, $fw_conf);
52}
53
009ee3ac
DM
54my $additional_param_hash = {};
55
56sub additional_parameters {
57 my ($class, $new_value) = @_;
58
59 if (defined($new_value)) {
60 $additional_param_hash->{$class} = $new_value;
61 }
62
63 # return a copy
64 my $copy = {};
65 my $org = $additional_param_hash->{$class} || {};
66 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
67 return $copy;
68}
69
70sub register_get_ipset {
71 my ($class) = @_;
72
73 my $properties = $class->additional_parameters();
74
75 $properties->{name} = $api_properties->{name};
76
77 $class->register_method({
78 name => 'get_ipset',
79 path => '',
80 method => 'GET',
81 description => "List IPSet content",
82 parameters => {
83 additionalProperties => 0,
84 properties => $properties,
85 },
86 returns => {
87 type => 'array',
88 items => {
89 type => "object",
90 properties => {
91 cidr => {
92 type => 'string',
93 },
94 comment => {
95 type => 'string',
96 optional => 1,
97 },
98 nomatch => {
99 type => 'boolean',
100 optional => 1,
d72c631c
DM
101 },
102 digest => get_standard_option('pve-config-digest', { optional => 0} ),
009ee3ac
DM
103 },
104 },
105 links => [ { rel => 'child', href => "{cidr}" } ],
106 },
107 code => sub {
108 my ($param) = @_;
109
1210ae94 110 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
009ee3ac 111
5d38d64f 112 return PVE::Firewall::copy_list_with_digest($ipset);
009ee3ac
DM
113 }});
114}
115
1210ae94
DM
116sub register_delete_ipset {
117 my ($class) = @_;
118
119 my $properties = $class->additional_parameters();
120
121 $properties->{name} = get_standard_option('ipset-name');
122
123 $class->register_method({
124 name => 'delete_ipset',
125 path => '',
126 method => 'DELETE',
127 description => "Delete IPSet",
128 protected => 1,
129 parameters => {
130 additionalProperties => 0,
131 properties => $properties,
132 },
133 returns => { type => 'null' },
134 code => sub {
135 my ($param) = @_;
136
137 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
138
139 die "IPSet '$param->{name}' is not empty\n"
140 if scalar(@$ipset);
141
142 $class->save_ipset($param, $fw_conf, undef);
143
144 return undef;
145 }});
146}
147
7c619bbb
DM
148my $verify_alias_exists = sub {
149 my ($cluster_conf, $fw_conf, $cidr) = @_;
150
151 if ($cidr !~ m/^\d/) {
152 my $alias = lc($cidr);
153 die "no such alias '$cidr'\n"
154 if !(($cluster_conf && $cluster_conf->{aliases}->{$alias}) ||
155 ($fw_conf && $fw_conf->{aliases}->{$alias}));
156 }
157};
158
a33c74f6 159sub register_create_ip {
009ee3ac
DM
160 my ($class) = @_;
161
162 my $properties = $class->additional_parameters();
163
164 $properties->{name} = $api_properties->{name};
165 $properties->{cidr} = $api_properties->{cidr};
166 $properties->{nomatch} = $api_properties->{nomatch};
167 $properties->{comment} = $api_properties->{comment};
d72c631c 168
009ee3ac 169 $class->register_method({
a33c74f6 170 name => 'create_ip',
009ee3ac
DM
171 path => '',
172 method => 'POST',
173 description => "Add IP or Network to IPSet.",
174 protected => 1,
175 parameters => {
176 additionalProperties => 0,
177 properties => $properties,
178 },
179 returns => { type => "null" },
180 code => sub {
181 my ($param) = @_;
182
1210ae94 183 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
009ee3ac 184
4a11bba5
DM
185 my $cidr = $param->{cidr};
186
187 foreach my $entry (@$ipset) {
188 raise_param_exc({ cidr => "address '$cidr' already exists" })
189 if $entry->{cidr} eq $cidr;
190 }
191
7c619bbb
DM
192 &$verify_alias_exists($cluster_conf, $fw_conf, $cidr);
193
4a11bba5 194 my $data = { cidr => $cidr };
7c619bbb 195
009ee3ac
DM
196 $data->{nomatch} = 1 if $param->{nomatch};
197 $data->{comment} = $param->{comment} if $param->{comment};
198
009ee3ac
DM
199 unshift @$ipset, $data;
200
201 $class->save_ipset($param, $fw_conf, $ipset);
202
203 return undef;
204 }});
205}
206
a33c74f6
DM
207sub register_read_ip {
208 my ($class) = @_;
209
210 my $properties = $class->additional_parameters();
211
212 $properties->{name} = $api_properties->{name};
213 $properties->{cidr} = $api_properties->{cidr};
214
215 $class->register_method({
216 name => 'read_ip',
217 path => '{cidr}',
218 method => 'GET',
219 description => "Read IP or Network settings from IPSet.",
220 protected => 1,
221 parameters => {
222 additionalProperties => 0,
223 properties => $properties,
224 },
225 returns => { type => "object" },
226 code => sub {
227 my ($param) = @_;
228
1210ae94 229 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
a33c74f6 230
5d38d64f
DM
231 my $list = PVE::Firewall::copy_list_with_digest($ipset);
232
233 foreach my $entry (@$list) {
d72c631c 234 if ($entry->{cidr} eq $param->{cidr}) {
d72c631c
DM
235 return $entry;
236 }
a33c74f6
DM
237 }
238
239 raise_param_exc({ cidr => "no such IP/Network" });
240 }});
241}
242
243sub register_update_ip {
244 my ($class) = @_;
245
246 my $properties = $class->additional_parameters();
247
248 $properties->{name} = $api_properties->{name};
249 $properties->{cidr} = $api_properties->{cidr};
250 $properties->{nomatch} = $api_properties->{nomatch};
251 $properties->{comment} = $api_properties->{comment};
d72c631c
DM
252 $properties->{digest} = get_standard_option('pve-config-digest');
253
a33c74f6
DM
254 $class->register_method({
255 name => 'update_ip',
256 path => '{cidr}',
257 method => 'PUT',
258 description => "Update IP or Network settings",
259 protected => 1,
260 parameters => {
261 additionalProperties => 0,
262 properties => $properties,
263 },
264 returns => { type => "null" },
265 code => sub {
266 my ($param) = @_;
267
1210ae94 268 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
a33c74f6 269
5d38d64f
DM
270 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
271 PVE::Tools::assert_if_modified($digest, $param->{digest});
d72c631c 272
a33c74f6
DM
273 foreach my $entry (@$ipset) {
274 if($entry->{cidr} eq $param->{cidr}) {
275 $entry->{nomatch} = $param->{nomatch};
276 $entry->{comment} = $param->{comment};
277 $class->save_ipset($param, $fw_conf, $ipset);
278 return;
279 }
280 }
281
282 raise_param_exc({ cidr => "no such IP/Network" });
283 }});
284}
285
286sub register_delete_ip {
009ee3ac
DM
287 my ($class) = @_;
288
289 my $properties = $class->additional_parameters();
290
291 $properties->{name} = $api_properties->{name};
292 $properties->{cidr} = $api_properties->{cidr};
d72c631c
DM
293 $properties->{digest} = get_standard_option('pve-config-digest');
294
009ee3ac
DM
295 $class->register_method({
296 name => 'remove_ip',
297 path => '{cidr}',
298 method => 'DELETE',
299 description => "Remove IP or Network from IPSet.",
300 protected => 1,
301 parameters => {
302 additionalProperties => 0,
303 properties => $properties,
304 },
305 returns => { type => "null" },
306 code => sub {
307 my ($param) = @_;
308
1210ae94 309 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
009ee3ac 310
5d38d64f
DM
311 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
312 PVE::Tools::assert_if_modified($digest, $param->{digest});
d72c631c 313
4a11bba5
DM
314 my $new = [];
315
316 foreach my $entry (@$ipset) {
317 push @$new, $entry if $entry->{cidr} ne $param->{cidr};
318 }
009ee3ac 319
4a11bba5
DM
320 $class->save_ipset($param, $fw_conf, $new);
321
009ee3ac
DM
322 return undef;
323 }});
324}
325
326sub register_handlers {
327 my ($class) = @_;
328
1210ae94 329 $class->register_delete_ipset();
009ee3ac 330 $class->register_get_ipset();
a33c74f6
DM
331 $class->register_create_ip();
332 $class->register_read_ip();
333 $class->register_update_ip();
334 $class->register_delete_ip();
009ee3ac
DM
335}
336
337package PVE::API2::Firewall::ClusterIPset;
338
339use strict;
340use warnings;
341
342use base qw(PVE::API2::Firewall::IPSetBase);
343
344sub load_config {
345 my ($class, $param) = @_;
346
347 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
348 my $ipset = $fw_conf->{ipset}->{$param->{name}};
349 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
350
1210ae94 351 return (undef, $fw_conf, $ipset);
009ee3ac
DM
352}
353
1210ae94
DM
354sub save_config {
355 my ($class, $param, $fw_conf) = @_;
009ee3ac 356
009ee3ac
DM
357 PVE::Firewall::save_clusterfw_conf($fw_conf);
358}
359
360__PACKAGE__->register_handlers();
361
1210ae94
DM
362package PVE::API2::Firewall::VMIPset;
363
364use strict;
365use warnings;
366use PVE::JSONSchema qw(get_standard_option);
367
368use base qw(PVE::API2::Firewall::IPSetBase);
369
370__PACKAGE__->additional_parameters({
371 node => get_standard_option('pve-node'),
372 vmid => get_standard_option('pve-vmid'),
373});
374
375sub load_config {
376 my ($class, $param) = @_;
377
378 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
379 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'vm', $param->{vmid});
380 my $ipset = $fw_conf->{ipset}->{$param->{name}};
381 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
382
383 return ($cluster_conf, $fw_conf, $ipset);
384}
385
386sub save_config {
387 my ($class, $param, $fw_conf) = @_;
388
389 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
390}
391
392__PACKAGE__->register_handlers();
393
394package PVE::API2::Firewall::CTIPset;
395
396use strict;
397use warnings;
398use PVE::JSONSchema qw(get_standard_option);
399
400use base qw(PVE::API2::Firewall::IPSetBase);
401
402__PACKAGE__->additional_parameters({
403 node => get_standard_option('pve-node'),
404 vmid => get_standard_option('pve-vmid'),
405});
406
407sub load_config {
408 my ($class, $param) = @_;
409
410 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
411 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'ct', $param->{vmid});
412 my $ipset = $fw_conf->{ipset}->{$param->{name}};
413 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
414
415 return ($cluster_conf, $fw_conf, $ipset);
416}
417
418sub save_config {
419 my ($class, $param, $fw_conf) = @_;
420
421 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
422}
423
424__PACKAGE__->register_handlers();
425
c85c87f9
DM
426package PVE::API2::Firewall::BaseIPSetList;
427
428use strict;
429use warnings;
e74a87f5 430use PVE::JSONSchema qw(get_standard_option);
c85c87f9 431use PVE::Exception qw(raise_param_exc);
e74a87f5 432use PVE::Firewall;
c85c87f9
DM
433
434use base qw(PVE::RESTHandler);
435
1210ae94
DM
436sub load_config {
437 my ($class, $param) = @_;
438
439 die "implement this in subclass";
440
441 #return ($cluster_conf, $fw_conf);
442}
443
444sub save_config {
445 my ($class, $param, $fw_conf) = @_;
446
447 die "implement this in subclass";
448}
449
450my $additional_param_hash_list = {};
451
452sub additional_parameters {
453 my ($class, $new_value) = @_;
454
455 if (defined($new_value)) {
456 $additional_param_hash_list->{$class} = $new_value;
457 }
458
459 # return a copy
460 my $copy = {};
461 my $org = $additional_param_hash_list->{$class} || {};
462 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
463 return $copy;
464}
465
5d38d64f
DM
466my $get_ipset_list = sub {
467 my ($fw_conf) = @_;
468
469 my $res = [];
470 foreach my $name (keys %{$fw_conf->{ipset}}) {
471 my $data = {
472 name => $name,
473 };
474 if (my $comment = $fw_conf->{ipset_comments}->{$name}) {
475 $data->{comment} = $comment;
476 }
477 push @$res, $data;
478 }
479
480 my ($list, $digest) = PVE::Firewall::copy_list_with_digest($res);
481
482 return wantarray ? ($list, $digest) : $list;
483};
484
c85c87f9
DM
485sub register_index {
486 my ($class) = @_;
487
1210ae94
DM
488 my $properties = $class->additional_parameters();
489
c85c87f9
DM
490 $class->register_method({
491 name => 'ipset_index',
492 path => '',
493 method => 'GET',
494 description => "List IPSets",
495 parameters => {
496 additionalProperties => 0,
1210ae94 497 properties => $properties,
c85c87f9
DM
498 },
499 returns => {
500 type => 'array',
501 items => {
502 type => "object",
503 properties => {
e74a87f5 504 name => get_standard_option('ipset-name'),
d72c631c
DM
505 digest => get_standard_option('pve-config-digest', { optional => 0} ),
506 comment => {
507 type => 'string',
508 optional => 1,
509 }
c85c87f9
DM
510 },
511 },
512 links => [ { rel => 'child', href => "{name}" } ],
513 },
514 code => sub {
515 my ($param) = @_;
516
1210ae94 517 my ($cluster_conf, $fw_conf) = $class->load_config($param);
c85c87f9 518
5d38d64f 519 return &$get_ipset_list($fw_conf);
c85c87f9
DM
520 }});
521}
522
523sub register_create {
524 my ($class) = @_;
525
1210ae94
DM
526 my $properties = $class->additional_parameters();
527
528 $properties->{name} = get_standard_option('ipset-name');
529
530 $properties->{comment} = { type => 'string', optional => 1 };
531
532 $properties->{digest} = get_standard_option('pve-config-digest');
533
534 $properties->{rename} = get_standard_option('ipset-name', {
535 description => "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
536 optional => 1 });
537
c85c87f9
DM
538 $class->register_method({
539 name => 'create_ipset',
540 path => '',
541 method => 'POST',
542 description => "Create new IPSet",
543 protected => 1,
544 parameters => {
545 additionalProperties => 0,
1210ae94 546 properties => $properties,
c85c87f9
DM
547 },
548 returns => { type => 'null' },
549 code => sub {
550 my ($param) = @_;
551
1210ae94 552 my ($cluster_conf, $fw_conf) = $class->load_config($param);
c85c87f9 553
bc374ca7 554 if ($param->{rename}) {
5d38d64f
DM
555 my (undef, $digest) = &$get_ipset_list($fw_conf);
556 PVE::Tools::assert_if_modified($digest, $param->{digest});
557
bc374ca7
DM
558 raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" })
559 if !$fw_conf->{ipset}->{$param->{rename}};
5d38d64f 560
bc374ca7
DM
561 my $data = delete $fw_conf->{ipset}->{$param->{rename}};
562 $fw_conf->{ipset}->{$param->{name}} = $data;
d72c631c
DM
563 if (my $comment = delete $fw_conf->{ipset_comments}->{$param->{rename}}) {
564 $fw_conf->{ipset_comments}->{$param->{name}} = $comment;
565 }
566 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
5d38d64f
DM
567 } else {
568 foreach my $name (keys %{$fw_conf->{ipset}}) {
569 raise_param_exc({ name => "IPSet '$name' already exists" })
570 if $name eq $param->{name};
571 }
572
bc374ca7 573 $fw_conf->{ipset}->{$param->{name}} = [];
d72c631c 574 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
bc374ca7
DM
575 }
576
1210ae94 577 $class->save_config($param, $fw_conf);
c85c87f9
DM
578
579 return undef;
580 }});
581}
582
1210ae94 583sub register_handlers {
c85c87f9
DM
584 my ($class) = @_;
585
1210ae94
DM
586 $class->register_index();
587 $class->register_create();
588}
c85c87f9 589
1210ae94 590package PVE::API2::Firewall::ClusterIPSetList;
c85c87f9 591
1210ae94
DM
592use strict;
593use warnings;
594use PVE::Firewall;
5d38d64f 595
1210ae94
DM
596use base qw(PVE::API2::Firewall::BaseIPSetList);
597
598sub load_config {
599 my ($class, $param) = @_;
600
601 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
602 return (undef, $cluster_conf);
603}
c85c87f9 604
1210ae94
DM
605sub save_config {
606 my ($class, $param, $fw_conf) = @_;
c85c87f9 607
1210ae94
DM
608 PVE::Firewall::save_clusterfw_conf($fw_conf);
609}
c85c87f9 610
1210ae94
DM
611__PACKAGE__->register_handlers();
612
613__PACKAGE__->register_method ({
614 subclass => "PVE::API2::Firewall::ClusterIPset",
615 path => '{name}',
616 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
617 fragmentDelimiter => '',
618});
619
620package PVE::API2::Firewall::VMIPSetList;
621
622use strict;
623use warnings;
624use PVE::JSONSchema qw(get_standard_option);
625use PVE::Firewall;
626
627use base qw(PVE::API2::Firewall::BaseIPSetList);
628
629__PACKAGE__->additional_parameters({
630 node => get_standard_option('pve-node'),
631 vmid => get_standard_option('pve-vmid'),
632});
633
634sub load_config {
635 my ($class, $param) = @_;
636
637 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
638 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'vm', $param->{vmid});
639 return ($cluster_conf, $fw_conf);
c85c87f9
DM
640}
641
1210ae94
DM
642sub save_config {
643 my ($class, $param, $fw_conf) = @_;
c85c87f9 644
1210ae94 645 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
c85c87f9
DM
646}
647
1210ae94
DM
648__PACKAGE__->register_handlers();
649
650__PACKAGE__->register_method ({
651 subclass => "PVE::API2::Firewall::VMIPset",
652 path => '{name}',
653 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
654 fragmentDelimiter => '',
655});
656
657package PVE::API2::Firewall::CTIPSetList;
c85c87f9
DM
658
659use strict;
660use warnings;
1210ae94 661use PVE::JSONSchema qw(get_standard_option);
c85c87f9
DM
662use PVE::Firewall;
663
664use base qw(PVE::API2::Firewall::BaseIPSetList);
665
1210ae94
DM
666__PACKAGE__->additional_parameters({
667 node => get_standard_option('pve-node'),
668 vmid => get_standard_option('pve-vmid'),
669});
670
c85c87f9 671sub load_config {
1210ae94 672 my ($class, $param) = @_;
c85c87f9 673
1210ae94
DM
674 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
675 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'ct', $param->{vmid});
676 return ($cluster_conf, $fw_conf);
c85c87f9
DM
677}
678
679sub save_config {
1210ae94 680 my ($class, $param, $fw_conf) = @_;
c85c87f9 681
1210ae94 682 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
c85c87f9
DM
683}
684
685__PACKAGE__->register_handlers();
686
687__PACKAGE__->register_method ({
1210ae94 688 subclass => "PVE::API2::Firewall::CTIPset",
c85c87f9
DM
689 path => '{name}',
690 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
691 fragmentDelimiter => '',
692});
693
009ee3ac 6941;