]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/API2/Firewall/IPSet.pm
Firewall/Rules: add permissions
[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
a33c74f6 148sub register_create_ip {
009ee3ac
DM
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};
d72c631c 157
009ee3ac 158 $class->register_method({
a33c74f6 159 name => 'create_ip',
009ee3ac
DM
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
1210ae94 172 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
009ee3ac 173
4a11bba5
DM
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
6c221576
DM
181 # make sure alias exists (if $cidr is an alias)
182 PVE::Firewall::resolve_alias($cluster_conf, $fw_conf, $cidr);
7c619bbb 183
4a11bba5 184 my $data = { cidr => $cidr };
7c619bbb 185
009ee3ac
DM
186 $data->{nomatch} = 1 if $param->{nomatch};
187 $data->{comment} = $param->{comment} if $param->{comment};
188
009ee3ac
DM
189 unshift @$ipset, $data;
190
191 $class->save_ipset($param, $fw_conf, $ipset);
192
193 return undef;
194 }});
195}
196
a33c74f6
DM
197sub 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
1210ae94 219 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
a33c74f6 220
5d38d64f
DM
221 my $list = PVE::Firewall::copy_list_with_digest($ipset);
222
223 foreach my $entry (@$list) {
d72c631c 224 if ($entry->{cidr} eq $param->{cidr}) {
d72c631c
DM
225 return $entry;
226 }
a33c74f6
DM
227 }
228
229 raise_param_exc({ cidr => "no such IP/Network" });
230 }});
231}
232
233sub 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};
d72c631c
DM
242 $properties->{digest} = get_standard_option('pve-config-digest');
243
a33c74f6
DM
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
1210ae94 258 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
a33c74f6 259
5d38d64f
DM
260 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
261 PVE::Tools::assert_if_modified($digest, $param->{digest});
d72c631c 262
a33c74f6
DM
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
276sub register_delete_ip {
009ee3ac
DM
277 my ($class) = @_;
278
279 my $properties = $class->additional_parameters();
280
281 $properties->{name} = $api_properties->{name};
282 $properties->{cidr} = $api_properties->{cidr};
d72c631c
DM
283 $properties->{digest} = get_standard_option('pve-config-digest');
284
009ee3ac
DM
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
1210ae94 299 my ($cluster_conf, $fw_conf, $ipset) = $class->load_config($param);
009ee3ac 300
5d38d64f
DM
301 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
302 PVE::Tools::assert_if_modified($digest, $param->{digest});
d72c631c 303
4a11bba5
DM
304 my $new = [];
305
306 foreach my $entry (@$ipset) {
307 push @$new, $entry if $entry->{cidr} ne $param->{cidr};
308 }
009ee3ac 309
4a11bba5
DM
310 $class->save_ipset($param, $fw_conf, $new);
311
009ee3ac
DM
312 return undef;
313 }});
314}
315
316sub register_handlers {
317 my ($class) = @_;
318
1210ae94 319 $class->register_delete_ipset();
009ee3ac 320 $class->register_get_ipset();
a33c74f6
DM
321 $class->register_create_ip();
322 $class->register_read_ip();
323 $class->register_update_ip();
324 $class->register_delete_ip();
009ee3ac
DM
325}
326
327package PVE::API2::Firewall::ClusterIPset;
328
329use strict;
330use warnings;
331
332use base qw(PVE::API2::Firewall::IPSetBase);
333
334sub 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
1210ae94 341 return (undef, $fw_conf, $ipset);
009ee3ac
DM
342}
343
1210ae94
DM
344sub save_config {
345 my ($class, $param, $fw_conf) = @_;
009ee3ac 346
009ee3ac
DM
347 PVE::Firewall::save_clusterfw_conf($fw_conf);
348}
349
350__PACKAGE__->register_handlers();
351
1210ae94
DM
352package PVE::API2::Firewall::VMIPset;
353
354use strict;
355use warnings;
356use PVE::JSONSchema qw(get_standard_option);
357
358use 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
365sub 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
376sub 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
384package PVE::API2::Firewall::CTIPset;
385
386use strict;
387use warnings;
388use PVE::JSONSchema qw(get_standard_option);
389
390use 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
397sub 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
408sub 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
c85c87f9
DM
416package PVE::API2::Firewall::BaseIPSetList;
417
418use strict;
419use warnings;
e74a87f5 420use PVE::JSONSchema qw(get_standard_option);
c85c87f9 421use PVE::Exception qw(raise_param_exc);
e74a87f5 422use PVE::Firewall;
c85c87f9
DM
423
424use base qw(PVE::RESTHandler);
425
1210ae94
DM
426sub load_config {
427 my ($class, $param) = @_;
428
429 die "implement this in subclass";
430
431 #return ($cluster_conf, $fw_conf);
432}
433
434sub save_config {
435 my ($class, $param, $fw_conf) = @_;
436
437 die "implement this in subclass";
438}
439
440my $additional_param_hash_list = {};
441
442sub 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
5d38d64f
DM
456my $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
c85c87f9
DM
475sub register_index {
476 my ($class) = @_;
477
1210ae94
DM
478 my $properties = $class->additional_parameters();
479
c85c87f9
DM
480 $class->register_method({
481 name => 'ipset_index',
482 path => '',
483 method => 'GET',
484 description => "List IPSets",
485 parameters => {
486 additionalProperties => 0,
1210ae94 487 properties => $properties,
c85c87f9
DM
488 },
489 returns => {
490 type => 'array',
491 items => {
492 type => "object",
493 properties => {
e74a87f5 494 name => get_standard_option('ipset-name'),
d72c631c
DM
495 digest => get_standard_option('pve-config-digest', { optional => 0} ),
496 comment => {
497 type => 'string',
498 optional => 1,
499 }
c85c87f9
DM
500 },
501 },
502 links => [ { rel => 'child', href => "{name}" } ],
503 },
504 code => sub {
505 my ($param) = @_;
506
1210ae94 507 my ($cluster_conf, $fw_conf) = $class->load_config($param);
c85c87f9 508
5d38d64f 509 return &$get_ipset_list($fw_conf);
c85c87f9
DM
510 }});
511}
512
513sub register_create {
514 my ($class) = @_;
515
1210ae94
DM
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
c85c87f9
DM
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,
1210ae94 536 properties => $properties,
c85c87f9
DM
537 },
538 returns => { type => 'null' },
539 code => sub {
540 my ($param) = @_;
541
1210ae94 542 my ($cluster_conf, $fw_conf) = $class->load_config($param);
c85c87f9 543
bc374ca7 544 if ($param->{rename}) {
5d38d64f
DM
545 my (undef, $digest) = &$get_ipset_list($fw_conf);
546 PVE::Tools::assert_if_modified($digest, $param->{digest});
547
bc374ca7
DM
548 raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" })
549 if !$fw_conf->{ipset}->{$param->{rename}};
5d38d64f 550
bc374ca7
DM
551 my $data = delete $fw_conf->{ipset}->{$param->{rename}};
552 $fw_conf->{ipset}->{$param->{name}} = $data;
d72c631c
DM
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});
5d38d64f
DM
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
bc374ca7 563 $fw_conf->{ipset}->{$param->{name}} = [];
d72c631c 564 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
bc374ca7
DM
565 }
566
1210ae94 567 $class->save_config($param, $fw_conf);
c85c87f9
DM
568
569 return undef;
570 }});
571}
572
1210ae94 573sub register_handlers {
c85c87f9
DM
574 my ($class) = @_;
575
1210ae94
DM
576 $class->register_index();
577 $class->register_create();
578}
c85c87f9 579
1210ae94 580package PVE::API2::Firewall::ClusterIPSetList;
c85c87f9 581
1210ae94
DM
582use strict;
583use warnings;
584use PVE::Firewall;
5d38d64f 585
1210ae94
DM
586use base qw(PVE::API2::Firewall::BaseIPSetList);
587
588sub load_config {
589 my ($class, $param) = @_;
590
591 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
592 return (undef, $cluster_conf);
593}
c85c87f9 594
1210ae94
DM
595sub save_config {
596 my ($class, $param, $fw_conf) = @_;
c85c87f9 597
1210ae94
DM
598 PVE::Firewall::save_clusterfw_conf($fw_conf);
599}
c85c87f9 600
1210ae94
DM
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
610package PVE::API2::Firewall::VMIPSetList;
611
612use strict;
613use warnings;
614use PVE::JSONSchema qw(get_standard_option);
615use PVE::Firewall;
616
617use 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
624sub 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);
c85c87f9
DM
630}
631
1210ae94
DM
632sub save_config {
633 my ($class, $param, $fw_conf) = @_;
c85c87f9 634
1210ae94 635 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
c85c87f9
DM
636}
637
1210ae94
DM
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
647package PVE::API2::Firewall::CTIPSetList;
c85c87f9
DM
648
649use strict;
650use warnings;
1210ae94 651use PVE::JSONSchema qw(get_standard_option);
c85c87f9
DM
652use PVE::Firewall;
653
654use base qw(PVE::API2::Firewall::BaseIPSetList);
655
1210ae94
DM
656__PACKAGE__->additional_parameters({
657 node => get_standard_option('pve-node'),
658 vmid => get_standard_option('pve-vmid'),
659});
660
c85c87f9 661sub load_config {
1210ae94 662 my ($class, $param) = @_;
c85c87f9 663
1210ae94
DM
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);
c85c87f9
DM
667}
668
669sub save_config {
1210ae94 670 my ($class, $param, $fw_conf) = @_;
c85c87f9 671
1210ae94 672 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
c85c87f9
DM
673}
674
675__PACKAGE__->register_handlers();
676
677__PACKAGE__->register_method ({
1210ae94 678 subclass => "PVE::API2::Firewall::CTIPset",
c85c87f9
DM
679 path => '{name}',
680 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
681 fragmentDelimiter => '',
682});
683
009ee3ac 6841;