fix #967: source: dest: limit length
[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 'icmp-type' => {
155 type => 'string',
156 optional => 1,
157 },
158 iface => {
159 type => 'string',
160 optional => 1,
161 },
162 ipversion => {
163 type => 'integer',
164 optional => 1,
165 },
166 macro => {
167 type => 'string',
168 optional => 1,
169 },
170 pos => {
171 type => 'integer',
172 },
173 proto => {
174 type => 'string',
175 optional => 1,
176 },
177 source => {
178 type => 'string',
179 optional => 1,
180 },
181 sport => {
182 type => 'string',
183 optional => 1,
184 },
185 type => {
186 type => 'string',
187 },
188 },
189 },
190 code => sub {
191 my ($param) = @_;
192
193 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
194
195 my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules);
196
197 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$list);
198
199 my $rule = $list->[$param->{pos}];
200 $rule->{pos} = $param->{pos};
201
202 return $rule;
203 }});
204 }
205
206 sub register_create_rule {
207 my ($class) = @_;
208
209 my $properties = $class->additional_parameters();
210
211 my $create_rule_properties = PVE::Firewall::add_rule_properties($properties);
212 $create_rule_properties->{action}->{optional} = 0;
213 $create_rule_properties->{type}->{optional} = 0;
214
215 my $rule_env = $class->rule_env();
216
217 $class->register_method({
218 name => 'create_rule',
219 path => '',
220 method => 'POST',
221 description => "Create new rule.",
222 protected => 1,
223 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
224 parameters => {
225 additionalProperties => 0,
226 properties => $create_rule_properties,
227 },
228 proxyto => $rule_env eq 'host' ? 'node' : undef,
229 returns => { type => "null" },
230 code => sub {
231 my ($param) = @_;
232
233 $class->lock_config($param, sub {
234 my ($param) = @_;
235
236 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
237
238 my $rule = {};
239
240 PVE::Firewall::copy_rule_data($rule, $param);
241 PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env());
242
243 $rule->{enable} = 0 if !defined($param->{enable});
244
245 unshift @$rules, $rule;
246
247 $class->save_rules($param, $fw_conf, $rules);
248 });
249
250 return undef;
251 }});
252 }
253
254 sub register_update_rule {
255 my ($class) = @_;
256
257 my $properties = $class->additional_parameters();
258
259 $properties->{pos} = $api_properties->{pos};
260
261 my $rule_env = $class->rule_env();
262
263 $properties->{moveto} = {
264 description => "Move rule to new position <moveto>. Other arguments are ignored.",
265 type => 'integer',
266 minimum => 0,
267 optional => 1,
268 };
269
270 $properties->{delete} = {
271 type => 'string', format => 'pve-configid-list',
272 description => "A list of settings you want to delete.",
273 optional => 1,
274 };
275
276 my $update_rule_properties = PVE::Firewall::add_rule_properties($properties);
277
278 $class->register_method({
279 name => 'update_rule',
280 path => '{pos}',
281 method => 'PUT',
282 description => "Modify rule data.",
283 protected => 1,
284 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
285 parameters => {
286 additionalProperties => 0,
287 properties => $update_rule_properties,
288 },
289 proxyto => $rule_env eq 'host' ? 'node' : undef,
290 returns => { type => "null" },
291 code => sub {
292 my ($param) = @_;
293
294 $class->lock_config($param, sub {
295 my ($param) = @_;
296
297 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
298
299 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
300 PVE::Tools::assert_if_modified($digest, $param->{digest});
301
302 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
303
304 my $rule = $rules->[$param->{pos}];
305
306 my $moveto = $param->{moveto};
307 if (defined($moveto) && $moveto != $param->{pos}) {
308 my $newrules = [];
309 for (my $i = 0; $i < scalar(@$rules); $i++) {
310 next if $i == $param->{pos};
311 if ($i == $moveto) {
312 push @$newrules, $rule;
313 }
314 push @$newrules, $rules->[$i];
315 }
316 push @$newrules, $rule if $moveto >= scalar(@$rules);
317 $rules = $newrules;
318 } else {
319 PVE::Firewall::copy_rule_data($rule, $param);
320
321 PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'};
322
323 PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env());
324 }
325
326 $class->save_rules($param, $fw_conf, $rules);
327 });
328
329 return undef;
330 }});
331 }
332
333 sub register_delete_rule {
334 my ($class) = @_;
335
336 my $properties = $class->additional_parameters();
337
338 $properties->{pos} = $api_properties->{pos};
339
340 $properties->{digest} = get_standard_option('pve-config-digest');
341
342 my $rule_env = $class->rule_env();
343
344 $class->register_method({
345 name => 'delete_rule',
346 path => '{pos}',
347 method => 'DELETE',
348 description => "Delete rule.",
349 protected => 1,
350 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
351 parameters => {
352 additionalProperties => 0,
353 properties => $properties,
354 },
355 proxyto => $rule_env eq 'host' ? 'node' : undef,
356 returns => { type => "null" },
357 code => sub {
358 my ($param) = @_;
359
360 $class->lock_config($param, sub {
361 my ($param) = @_;
362
363 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
364
365 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
366 PVE::Tools::assert_if_modified($digest, $param->{digest});
367
368 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
369
370 splice(@$rules, $param->{pos}, 1);
371
372 $class->save_rules($param, $fw_conf, $rules);
373 });
374
375 return undef;
376 }});
377 }
378
379 sub register_handlers {
380 my ($class) = @_;
381
382 $class->register_get_rules();
383 $class->register_get_rule();
384 $class->register_create_rule();
385 $class->register_update_rule();
386 $class->register_delete_rule();
387 }
388
389 package PVE::API2::Firewall::GroupRules;
390
391 use strict;
392 use warnings;
393 use PVE::JSONSchema qw(get_standard_option);
394
395 use base qw(PVE::API2::Firewall::RulesBase);
396
397 __PACKAGE__->additional_parameters({ group => get_standard_option('pve-security-group-name') });
398
399
400 sub rule_env {
401 my ($class, $param) = @_;
402
403 return 'group';
404 }
405
406 sub lock_config {
407 my ($class, $param, $code) = @_;
408
409 PVE::Firewall::lock_clusterfw_conf(10, $code, $param);
410 }
411
412 sub load_config {
413 my ($class, $param) = @_;
414
415 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
416 my $rules = $fw_conf->{groups}->{$param->{group}};
417 die "no such security group '$param->{group}'\n" if !defined($rules);
418
419 return (undef, $fw_conf, $rules);
420 }
421
422 sub save_rules {
423 my ($class, $param, $fw_conf, $rules) = @_;
424
425 if (!defined($rules)) {
426 delete $fw_conf->{groups}->{$param->{group}};
427 } else {
428 $fw_conf->{groups}->{$param->{group}} = $rules;
429 }
430
431 PVE::Firewall::save_clusterfw_conf($fw_conf);
432 }
433
434 __PACKAGE__->register_method({
435 name => 'delete_security_group',
436 path => '',
437 method => 'DELETE',
438 description => "Delete security group.",
439 protected => 1,
440 permissions => {
441 check => ['perm', '/', [ 'Sys.Modify' ]],
442 },
443 parameters => {
444 additionalProperties => 0,
445 properties => {
446 group => get_standard_option('pve-security-group-name'),
447 },
448 },
449 returns => { type => 'null' },
450 code => sub {
451 my ($param) = @_;
452
453 __PACKAGE__->lock_config($param, sub {
454 my ($param) = @_;
455
456 my (undef, $cluster_conf, $rules) = __PACKAGE__->load_config($param);
457
458 die "Security group '$param->{group}' is not empty\n"
459 if scalar(@$rules);
460
461 __PACKAGE__->save_rules($param, $cluster_conf, undef);
462 });
463
464 return undef;
465 }});
466
467 __PACKAGE__->register_handlers();
468
469 package PVE::API2::Firewall::ClusterRules;
470
471 use strict;
472 use warnings;
473
474 use base qw(PVE::API2::Firewall::RulesBase);
475
476 sub rule_env {
477 my ($class, $param) = @_;
478
479 return 'cluster';
480 }
481
482 sub lock_config {
483 my ($class, $param, $code) = @_;
484
485 PVE::Firewall::lock_clusterfw_conf(10, $code, $param);
486 }
487
488 sub load_config {
489 my ($class, $param) = @_;
490
491 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
492 my $rules = $fw_conf->{rules};
493
494 return (undef, $fw_conf, $rules);
495 }
496
497 sub save_rules {
498 my ($class, $param, $fw_conf, $rules) = @_;
499
500 $fw_conf->{rules} = $rules;
501 PVE::Firewall::save_clusterfw_conf($fw_conf);
502 }
503
504 __PACKAGE__->register_handlers();
505
506 package PVE::API2::Firewall::HostRules;
507
508 use strict;
509 use warnings;
510 use PVE::JSONSchema qw(get_standard_option);
511
512 use base qw(PVE::API2::Firewall::RulesBase);
513
514 __PACKAGE__->additional_parameters({ node => get_standard_option('pve-node')});
515
516 sub rule_env {
517 my ($class, $param) = @_;
518
519 return 'host';
520 }
521
522 sub lock_config {
523 my ($class, $param, $code) = @_;
524
525 PVE::Firewall::lock_hostfw_conf(10, $code, $param);
526 }
527
528 sub load_config {
529 my ($class, $param) = @_;
530
531 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
532 my $fw_conf = PVE::Firewall::load_hostfw_conf($cluster_conf);
533 my $rules = $fw_conf->{rules};
534
535 return ($cluster_conf, $fw_conf, $rules);
536 }
537
538 sub save_rules {
539 my ($class, $param, $fw_conf, $rules) = @_;
540
541 $fw_conf->{rules} = $rules;
542 PVE::Firewall::save_hostfw_conf($fw_conf);
543 }
544
545 __PACKAGE__->register_handlers();
546
547 package PVE::API2::Firewall::VMRules;
548
549 use strict;
550 use warnings;
551 use PVE::JSONSchema qw(get_standard_option);
552
553 use base qw(PVE::API2::Firewall::RulesBase);
554
555 __PACKAGE__->additional_parameters({
556 node => get_standard_option('pve-node'),
557 vmid => get_standard_option('pve-vmid'),
558 });
559
560 sub rule_env {
561 my ($class, $param) = @_;
562
563 return 'vm';
564 }
565
566 sub lock_config {
567 my ($class, $param, $code) = @_;
568
569 PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param);
570 }
571
572 sub load_config {
573 my ($class, $param) = @_;
574
575 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
576 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'vm', $param->{vmid});
577 my $rules = $fw_conf->{rules};
578
579 return ($cluster_conf, $fw_conf, $rules);
580 }
581
582 sub save_rules {
583 my ($class, $param, $fw_conf, $rules) = @_;
584
585 $fw_conf->{rules} = $rules;
586 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
587 }
588
589 __PACKAGE__->register_handlers();
590
591 package PVE::API2::Firewall::CTRules;
592
593 use strict;
594 use warnings;
595 use PVE::JSONSchema qw(get_standard_option);
596
597 use base qw(PVE::API2::Firewall::RulesBase);
598
599 __PACKAGE__->additional_parameters({
600 node => get_standard_option('pve-node'),
601 vmid => get_standard_option('pve-vmid'),
602 });
603
604 sub rule_env {
605 my ($class, $param) = @_;
606
607 return 'ct';
608 }
609
610 sub lock_config {
611 my ($class, $param, $code) = @_;
612
613 PVE::Firewall::lock_vmfw_conf($param->{vmid}, 10, $code, $param);
614 }
615
616 sub load_config {
617 my ($class, $param) = @_;
618
619 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
620 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'ct', $param->{vmid});
621 my $rules = $fw_conf->{rules};
622
623 return ($cluster_conf, $fw_conf, $rules);
624 }
625
626 sub save_rules {
627 my ($class, $param, $fw_conf, $rules) = @_;
628
629 $fw_conf->{rules} = $rules;
630 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
631 }
632
633 __PACKAGE__->register_handlers();
634
635 1;