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