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