]> git.proxmox.com Git - pve-firewall.git/blob - src/PVE/API2/Firewall/Rules.pm
d/control: bump pve-cluster dependency for new lock methods
[pve-firewall.git] / src / PVE / API2 / Firewall / Rules.pm
1 package PVE::API2::Firewall::RulesBase;
2
3 use strict;
4 use warnings;
5
6 use PVE::JSONSchema qw(get_standard_option);
7 use PVE::Exception qw(raise raise_param_exc);
8
9 use PVE::Firewall;
10
11 use base qw(PVE::RESTHandler);
12
13 my $api_properties = {
14 pos => {
15 description => "Rule position.",
16 type => 'integer',
17 minimum => 0,
18 },
19 };
20
21 sub lock_config {
22 my ($class, $param, $code) = @_;
23
24 die "implement this in subclass";
25 }
26
27 sub load_config {
28 my ($class, $param) = @_;
29
30 die "implement this in subclass";
31
32 #return ($cluster_conf, $fw_conf, $rules);
33 }
34
35 sub save_rules {
36 my ($class, $param, $fw_conf, $rules) = @_;
37
38 die "implement this in subclass";
39 }
40
41 my $additional_param_hash = {};
42
43 sub rule_env {
44 my ($class, $param) = @_;
45
46 die "implement this in subclass";
47 }
48
49 sub additional_parameters {
50 my ($class, $new_value) = @_;
51
52 if (defined($new_value)) {
53 $additional_param_hash->{$class} = $new_value;
54 }
55
56 # return a copy
57 my $copy = {};
58 my $org = $additional_param_hash->{$class} || {};
59 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
60 return $copy;
61 }
62
63 sub register_get_rules {
64 my ($class) = @_;
65
66 my $properties = $class->additional_parameters();
67
68 my $rule_env = $class->rule_env();
69
70 $class->register_method({
71 name => 'get_rules',
72 path => '',
73 method => 'GET',
74 description => "List rules.",
75 permissions => PVE::Firewall::rules_audit_permissions($rule_env),
76 parameters => {
77 additionalProperties => 0,
78 properties => $properties,
79 },
80 proxyto => $rule_env eq 'host' ? 'node' : undef,
81 returns => {
82 type => 'array',
83 items => {
84 type => "object",
85 properties => {
86 pos => {
87 type => 'integer',
88 }
89 },
90 },
91 links => [ { rel => 'child', href => "{pos}" } ],
92 },
93 code => sub {
94 my ($param) = @_;
95
96 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
97
98 my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules);
99
100 my $ind = 0;
101 foreach my $rule (@$list) {
102 $rule->{pos} = $ind++;
103 }
104
105 return $list;
106 }});
107 }
108
109 sub register_get_rule {
110 my ($class) = @_;
111
112 my $properties = $class->additional_parameters();
113
114 $properties->{pos} = $api_properties->{pos};
115
116 my $rule_env = $class->rule_env();
117
118 $class->register_method({
119 name => 'get_rule',
120 path => '{pos}',
121 method => 'GET',
122 description => "Get single rule data.",
123 permissions => PVE::Firewall::rules_audit_permissions($rule_env),
124 parameters => {
125 additionalProperties => 0,
126 properties => $properties,
127 },
128 proxyto => $rule_env eq 'host' ? 'node' : undef,
129 returns => {
130 type => "object",
131 properties => {
132 action => {
133 type => 'string',
134 },
135 comment => {
136 type => 'string',
137 optional => 1,
138 },
139 dest => {
140 type => 'string',
141 optional => 1,
142 },
143 dport => {
144 type => 'string',
145 optional => 1,
146 },
147 enable => {
148 type => 'integer',
149 optional => 1,
150 },
151 log => PVE::Firewall::get_standard_option('pve-fw-loglevel', {
152 description => 'Log level for firewall rule',
153 }),
154 iface => {
155 type => 'string',
156 optional => 1,
157 },
158 ipversion => {
159 type => 'integer',
160 optional => 1,
161 },
162 macro => {
163 type => 'string',
164 optional => 1,
165 },
166 pos => {
167 type => 'integer',
168 },
169 proto => {
170 type => 'string',
171 optional => 1,
172 },
173 source => {
174 type => 'string',
175 optional => 1,
176 },
177 sport => {
178 type => 'string',
179 optional => 1,
180 },
181 type => {
182 type => 'string',
183 },
184 },
185 },
186 code => sub {
187 my ($param) = @_;
188
189 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
190
191 my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules);
192
193 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$list);
194
195 my $rule = $list->[$param->{pos}];
196 $rule->{pos} = $param->{pos};
197
198 return $rule;
199 }});
200 }
201
202 sub register_create_rule {
203 my ($class) = @_;
204
205 my $properties = $class->additional_parameters();
206
207 my $create_rule_properties = PVE::Firewall::add_rule_properties($properties);
208 $create_rule_properties->{action}->{optional} = 0;
209 $create_rule_properties->{type}->{optional} = 0;
210
211 my $rule_env = $class->rule_env();
212
213 $class->register_method({
214 name => 'create_rule',
215 path => '',
216 method => 'POST',
217 description => "Create new rule.",
218 protected => 1,
219 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
220 parameters => {
221 additionalProperties => 0,
222 properties => $create_rule_properties,
223 },
224 proxyto => $rule_env eq 'host' ? 'node' : undef,
225 returns => { type => "null" },
226 code => sub {
227 my ($param) = @_;
228
229 $class->lock_config($param, sub {
230 my ($param) = @_;
231
232 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
233
234 my $rule = {};
235
236 PVE::Firewall::copy_rule_data($rule, $param);
237 PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env());
238
239 $rule->{enable} = 0 if !defined($param->{enable});
240
241 unshift @$rules, $rule;
242
243 $class->save_rules($param, $fw_conf, $rules);
244 });
245
246 return undef;
247 }});
248 }
249
250 sub register_update_rule {
251 my ($class) = @_;
252
253 my $properties = $class->additional_parameters();
254
255 $properties->{pos} = $api_properties->{pos};
256
257 my $rule_env = $class->rule_env();
258
259 $properties->{moveto} = {
260 description => "Move rule to new position <moveto>. Other arguments are ignored.",
261 type => 'integer',
262 minimum => 0,
263 optional => 1,
264 };
265
266 $properties->{delete} = {
267 type => 'string', format => 'pve-configid-list',
268 description => "A list of settings you want to delete.",
269 optional => 1,
270 };
271
272 my $update_rule_properties = PVE::Firewall::add_rule_properties($properties);
273
274 $class->register_method({
275 name => 'update_rule',
276 path => '{pos}',
277 method => 'PUT',
278 description => "Modify rule data.",
279 protected => 1,
280 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
281 parameters => {
282 additionalProperties => 0,
283 properties => $update_rule_properties,
284 },
285 proxyto => $rule_env eq 'host' ? 'node' : undef,
286 returns => { type => "null" },
287 code => sub {
288 my ($param) = @_;
289
290 $class->lock_config($param, sub {
291 my ($param) = @_;
292
293 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
294
295 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
296 PVE::Tools::assert_if_modified($digest, $param->{digest});
297
298 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
299
300 my $rule = $rules->[$param->{pos}];
301
302 my $moveto = $param->{moveto};
303 if (defined($moveto) && $moveto != $param->{pos}) {
304 my $newrules = [];
305 for (my $i = 0; $i < scalar(@$rules); $i++) {
306 next if $i == $param->{pos};
307 if ($i == $moveto) {
308 push @$newrules, $rule;
309 }
310 push @$newrules, $rules->[$i];
311 }
312 push @$newrules, $rule if $moveto >= scalar(@$rules);
313 $rules = $newrules;
314 } else {
315 PVE::Firewall::copy_rule_data($rule, $param);
316
317 PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'};
318
319 PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env());
320 }
321
322 $class->save_rules($param, $fw_conf, $rules);
323 });
324
325 return undef;
326 }});
327 }
328
329 sub register_delete_rule {
330 my ($class) = @_;
331
332 my $properties = $class->additional_parameters();
333
334 $properties->{pos} = $api_properties->{pos};
335
336 $properties->{digest} = get_standard_option('pve-config-digest');
337
338 my $rule_env = $class->rule_env();
339
340 $class->register_method({
341 name => 'delete_rule',
342 path => '{pos}',
343 method => 'DELETE',
344 description => "Delete rule.",
345 protected => 1,
346 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
347 parameters => {
348 additionalProperties => 0,
349 properties => $properties,
350 },
351 proxyto => $rule_env eq 'host' ? 'node' : undef,
352 returns => { type => "null" },
353 code => sub {
354 my ($param) = @_;
355
356 $class->lock_config($param, sub {
357 my ($param) = @_;
358
359 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
360
361 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
362 PVE::Tools::assert_if_modified($digest, $param->{digest});
363
364 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
365
366 splice(@$rules, $param->{pos}, 1);
367
368 $class->save_rules($param, $fw_conf, $rules);
369 });
370
371 return undef;
372 }});
373 }
374
375 sub register_handlers {
376 my ($class) = @_;
377
378 $class->register_get_rules();
379 $class->register_get_rule();
380 $class->register_create_rule();
381 $class->register_update_rule();
382 $class->register_delete_rule();
383 }
384
385 package PVE::API2::Firewall::GroupRules;
386
387 use strict;
388 use warnings;
389 use PVE::JSONSchema qw(get_standard_option);
390
391 use base qw(PVE::API2::Firewall::RulesBase);
392
393 __PACKAGE__->additional_parameters({ group => get_standard_option('pve-security-group-name') });
394
395
396 sub rule_env {
397 my ($class, $param) = @_;
398
399 return 'group';
400 }
401
402 sub lock_config {
403 my ($class, $param, $code) = @_;
404
405 PVE::Firewall::lock_clusterfw_conf(10, $code, $param);
406 }
407
408 sub load_config {
409 my ($class, $param) = @_;
410
411 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
412 my $rules = $fw_conf->{groups}->{$param->{group}};
413 die "no such security group '$param->{group}'\n" if !defined($rules);
414
415 return (undef, $fw_conf, $rules);
416 }
417
418 sub save_rules {
419 my ($class, $param, $fw_conf, $rules) = @_;
420
421 if (!defined($rules)) {
422 delete $fw_conf->{groups}->{$param->{group}};
423 } else {
424 $fw_conf->{groups}->{$param->{group}} = $rules;
425 }
426
427 PVE::Firewall::save_clusterfw_conf($fw_conf);
428 }
429
430 __PACKAGE__->register_method({
431 name => 'delete_security_group',
432 path => '',
433 method => 'DELETE',
434 description => "Delete security group.",
435 protected => 1,
436 permissions => {
437 check => ['perm', '/', [ 'Sys.Modify' ]],
438 },
439 parameters => {
440 additionalProperties => 0,
441 properties => {
442 group => get_standard_option('pve-security-group-name'),
443 },
444 },
445 returns => { type => 'null' },
446 code => sub {
447 my ($param) = @_;
448
449 __PACKAGE__->lock_config($param, sub {
450 my ($param) = @_;
451
452 my (undef, $cluster_conf, $rules) = __PACKAGE__->load_config($param);
453
454 die "Security group '$param->{group}' is not empty\n"
455 if scalar(@$rules);
456
457 __PACKAGE__->save_rules($param, $cluster_conf, undef);
458 });
459
460 return undef;
461 }});
462
463 __PACKAGE__->register_handlers();
464
465 package PVE::API2::Firewall::ClusterRules;
466
467 use strict;
468 use warnings;
469
470 use base qw(PVE::API2::Firewall::RulesBase);
471
472 sub rule_env {
473 my ($class, $param) = @_;
474
475 return 'cluster';
476 }
477
478 sub lock_config {
479 my ($class, $param, $code) = @_;
480
481 PVE::Firewall::lock_clusterfw_conf(10, $code, $param);
482 }
483
484 sub load_config {
485 my ($class, $param) = @_;
486
487 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
488 my $rules = $fw_conf->{rules};
489
490 return (undef, $fw_conf, $rules);
491 }
492
493 sub save_rules {
494 my ($class, $param, $fw_conf, $rules) = @_;
495
496 $fw_conf->{rules} = $rules;
497 PVE::Firewall::save_clusterfw_conf($fw_conf);
498 }
499
500 __PACKAGE__->register_handlers();
501
502 package PVE::API2::Firewall::HostRules;
503
504 use strict;
505 use warnings;
506 use PVE::JSONSchema qw(get_standard_option);
507
508 use base qw(PVE::API2::Firewall::RulesBase);
509
510 __PACKAGE__->additional_parameters({ node => get_standard_option('pve-node')});
511
512 sub rule_env {
513 my ($class, $param) = @_;
514
515 return 'host';
516 }
517
518 sub lock_config {
519 my ($class, $param, $code) = @_;
520
521 PVE::Firewall::lock_hostfw_conf(10, $code, $param);
522 }
523
524 sub load_config {
525 my ($class, $param) = @_;
526
527 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
528 my $fw_conf = PVE::Firewall::load_hostfw_conf($cluster_conf);
529 my $rules = $fw_conf->{rules};
530
531 return ($cluster_conf, $fw_conf, $rules);
532 }
533
534 sub save_rules {
535 my ($class, $param, $fw_conf, $rules) = @_;
536
537 $fw_conf->{rules} = $rules;
538 PVE::Firewall::save_hostfw_conf($fw_conf);
539 }
540
541 __PACKAGE__->register_handlers();
542
543 package PVE::API2::Firewall::VMRules;
544
545 use strict;
546 use warnings;
547 use PVE::JSONSchema qw(get_standard_option);
548
549 use base qw(PVE::API2::Firewall::RulesBase);
550
551 __PACKAGE__->additional_parameters({
552 node => get_standard_option('pve-node'),
553 vmid => get_standard_option('pve-vmid'),
554 });
555
556 sub rule_env {
557 my ($class, $param) = @_;
558
559 return 'vm';
560 }
561
562 sub lock_config {
563 my ($class, $param, $code) = @_;
564
565 PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param);
566 }
567
568 sub load_config {
569 my ($class, $param) = @_;
570
571 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
572 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'vm', $param->{vmid});
573 my $rules = $fw_conf->{rules};
574
575 return ($cluster_conf, $fw_conf, $rules);
576 }
577
578 sub save_rules {
579 my ($class, $param, $fw_conf, $rules) = @_;
580
581 $fw_conf->{rules} = $rules;
582 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
583 }
584
585 __PACKAGE__->register_handlers();
586
587 package PVE::API2::Firewall::CTRules;
588
589 use strict;
590 use warnings;
591 use PVE::JSONSchema qw(get_standard_option);
592
593 use base qw(PVE::API2::Firewall::RulesBase);
594
595 __PACKAGE__->additional_parameters({
596 node => get_standard_option('pve-node'),
597 vmid => get_standard_option('pve-vmid'),
598 });
599
600 sub rule_env {
601 my ($class, $param) = @_;
602
603 return 'ct';
604 }
605
606 sub lock_config {
607 my ($class, $param, $code) = @_;
608
609 PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param);
610 }
611
612 sub load_config {
613 my ($class, $param) = @_;
614
615 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
616 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'ct', $param->{vmid});
617 my $rules = $fw_conf->{rules};
618
619 return ($cluster_conf, $fw_conf, $rules);
620 }
621
622 sub save_rules {
623 my ($class, $param, $fw_conf, $rules) = @_;
624
625 $fw_conf->{rules} = $rules;
626 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
627 }
628
629 __PACKAGE__->register_handlers();
630
631 1;