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;