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