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