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