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