]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/API2/Firewall/Rules.pm
configs: add locking helpers
[pve-firewall.git] / src / PVE / API2 / Firewall / Rules.pm
CommitLineData
86791289
DM
1package PVE::API2::Firewall::RulesBase;
2
3use strict;
4use warnings;
5use PVE::JSONSchema qw(get_standard_option);
0d22acb3 6use PVE::Exception qw(raise raise_param_exc);
86791289
DM
7
8use PVE::Firewall;
9
10use base qw(PVE::RESTHandler);
11
75a12a9d 12my $api_properties = {
86791289
DM
13 pos => {
14 description => "Rule position.",
15 type => 'integer',
16 minimum => 0,
17 },
18};
19
20sub load_config {
21 my ($class, $param) = @_;
22
23 die "implement this in subclass";
24
a523e057 25 #return ($cluster_conf, $fw_conf, $rules);
86791289
DM
26}
27
28sub save_rules {
29 my ($class, $param, $fw_conf, $rules) = @_;
30
31 die "implement this in subclass";
32}
33
63c91681 34my $additional_param_hash = {};
86791289 35
b6b8e6ad
DM
36sub rule_env {
37 my ($class, $param) = @_;
75a12a9d 38
b6b8e6ad 39 die "implement this in subclass";
7ca36671
DM
40}
41
63c91681 42sub additional_parameters {
86791289
DM
43 my ($class, $new_value) = @_;
44
63c91681
DM
45 if (defined($new_value)) {
46 $additional_param_hash->{$class} = $new_value;
47 }
86791289 48
63c91681
DM
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;
86791289
DM
54}
55
56sub register_get_rules {
57 my ($class) = @_;
58
63c91681 59 my $properties = $class->additional_parameters();
86791289 60
7f733a5a
DM
61 my $rule_env = $class->rule_env();
62
86791289
DM
63 $class->register_method({
64 name => 'get_rules',
65 path => '',
66 method => 'GET',
67 description => "List rules.",
9f6845cf 68 permissions => PVE::Firewall::rules_audit_permissions($rule_env),
86791289
DM
69 parameters => {
70 additionalProperties => 0,
71 properties => $properties,
72 },
7f733a5a 73 proxyto => $rule_env eq 'host' ? 'node' : undef,
86791289
DM
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
a523e057 89 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
86791289 90
5d38d64f 91 my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules);
86791289
DM
92
93 my $ind = 0;
5d38d64f
DM
94 foreach my $rule (@$list) {
95 $rule->{pos} = $ind++;
86791289
DM
96 }
97
5d38d64f 98 return $list;
86791289
DM
99 }});
100}
101
102sub register_get_rule {
103 my ($class) = @_;
104
63c91681 105 my $properties = $class->additional_parameters();
86791289
DM
106
107 $properties->{pos} = $api_properties->{pos};
75a12a9d 108
7f733a5a
DM
109 my $rule_env = $class->rule_env();
110
86791289
DM
111 $class->register_method({
112 name => 'get_rule',
113 path => '{pos}',
114 method => 'GET',
115 description => "Get single rule data.",
9f6845cf 116 permissions => PVE::Firewall::rules_audit_permissions($rule_env),
86791289
DM
117 parameters => {
118 additionalProperties => 0,
119 properties => $properties,
120 },
7f733a5a 121 proxyto => $rule_env eq 'host' ? 'node' : undef,
86791289
DM
122 returns => {
123 type => "object",
124 properties => {
c5b2e6d9
RV
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 },
3489f8a2
CE
144 log => PVE::Firewall::get_standard_option('pve-fw-loglevel', {
145 description => 'Log level for firewall rule',
146 }),
c5b2e6d9
RV
147 iface => {
148 type => 'string',
149 optional => 1,
150 },
151 ipversion => {
152 type => 'integer',
153 optional => 1,
154 },
155 macro => {
f0e30c99 156 type => 'string',
c5b2e6d9
RV
157 optional => 1,
158 },
86791289
DM
159 pos => {
160 type => 'integer',
c5b2e6d9
RV
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 },
86791289
DM
177 },
178 },
179 code => sub {
180 my ($param) = @_;
181
a523e057 182 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
86791289 183
5d38d64f 184 my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules);
75a12a9d 185
5d38d64f 186 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$list);
75a12a9d 187
5d38d64f
DM
188 my $rule = $list->[$param->{pos}];
189 $rule->{pos} = $param->{pos};
190
191 return $rule;
86791289
DM
192 }});
193}
194
195sub register_create_rule {
196 my ($class) = @_;
197
63c91681 198 my $properties = $class->additional_parameters();
86791289
DM
199
200 my $create_rule_properties = PVE::Firewall::add_rule_properties($properties);
3655b01f
DM
201 $create_rule_properties->{action}->{optional} = 0;
202 $create_rule_properties->{type}->{optional} = 0;
75a12a9d 203
7f733a5a
DM
204 my $rule_env = $class->rule_env();
205
86791289
DM
206 $class->register_method({
207 name => 'create_rule',
208 path => '',
209 method => 'POST',
210 description => "Create new rule.",
211 protected => 1,
9f6845cf 212 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
86791289
DM
213 parameters => {
214 additionalProperties => 0,
215 properties => $create_rule_properties,
216 },
7f733a5a 217 proxyto => $rule_env eq 'host' ? 'node' : undef,
86791289
DM
218 returns => { type => "null" },
219 code => sub {
220 my ($param) = @_;
221
a523e057 222 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
86791289 223
3655b01f 224 my $rule = {};
86791289
DM
225
226 PVE::Firewall::copy_rule_data($rule, $param);
a523e057 227 PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env());
86791289 228
3655b01f
DM
229 $rule->{enable} = 0 if !defined($param->{enable});
230
86791289
DM
231 unshift @$rules, $rule;
232
233 $class->save_rules($param, $fw_conf, $rules);
234
235 return undef;
236 }});
237}
238
239sub register_update_rule {
240 my ($class) = @_;
241
63c91681 242 my $properties = $class->additional_parameters();
86791289
DM
243
244 $properties->{pos} = $api_properties->{pos};
75a12a9d 245
7f733a5a
DM
246 my $rule_env = $class->rule_env();
247
86791289
DM
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
5b7974df
DM
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
86791289
DM
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,
9f6845cf 269 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
86791289
DM
270 parameters => {
271 additionalProperties => 0,
272 properties => $update_rule_properties,
273 },
7f733a5a 274 proxyto => $rule_env eq 'host' ? 'node' : undef,
86791289
DM
275 returns => { type => "null" },
276 code => sub {
277 my ($param) = @_;
278
a523e057 279 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
86791289 280
5d38d64f
DM
281 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
282 PVE::Tools::assert_if_modified($digest, $param->{digest});
ddf1e07d 283
86791289 284 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
75a12a9d 285
86791289
DM
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);
75a12a9d 302
5b7974df 303 PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'};
7ca36671 304
a523e057 305 PVE::Firewall::verify_rule($rule, $cluster_conf, $fw_conf, $class->rule_env());
86791289
DM
306 }
307
308 $class->save_rules($param, $fw_conf, $rules);
309
310 return undef;
311 }});
312}
313
314sub register_delete_rule {
315 my ($class) = @_;
316
63c91681 317 my $properties = $class->additional_parameters();
86791289
DM
318
319 $properties->{pos} = $api_properties->{pos};
ddf1e07d
DM
320
321 $properties->{digest} = get_standard_option('pve-config-digest');
75a12a9d 322
7f733a5a
DM
323 my $rule_env = $class->rule_env();
324
86791289
DM
325 $class->register_method({
326 name => 'delete_rule',
327 path => '{pos}',
328 method => 'DELETE',
329 description => "Delete rule.",
330 protected => 1,
9f6845cf 331 permissions => PVE::Firewall::rules_modify_permissions($rule_env),
86791289
DM
332 parameters => {
333 additionalProperties => 0,
334 properties => $properties,
335 },
7f733a5a 336 proxyto => $rule_env eq 'host' ? 'node' : undef,
86791289
DM
337 returns => { type => "null" },
338 code => sub {
339 my ($param) = @_;
340
a523e057 341 my ($cluster_conf, $fw_conf, $rules) = $class->load_config($param);
86791289 342
5d38d64f
DM
343 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
344 PVE::Tools::assert_if_modified($digest, $param->{digest});
75a12a9d 345
86791289 346 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
75a12a9d 347
86791289 348 splice(@$rules, $param->{pos}, 1);
75a12a9d 349
86791289
DM
350 $class->save_rules($param, $fw_conf, $rules);
351
352 return undef;
353 }});
354}
355
356sub 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
366package PVE::API2::Firewall::GroupRules;
367
368use strict;
369use warnings;
387d0ffc 370use PVE::JSONSchema qw(get_standard_option);
86791289
DM
371
372use base qw(PVE::API2::Firewall::RulesBase);
373
387d0ffc 374__PACKAGE__->additional_parameters({ group => get_standard_option('pve-security-group-name') });
86791289 375
1210ae94 376
b6b8e6ad
DM
377sub rule_env {
378 my ($class, $param) = @_;
75a12a9d 379
b6b8e6ad 380 return 'group';
7ca36671
DM
381}
382
86791289
DM
383sub 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
a523e057 390 return (undef, $fw_conf, $rules);
86791289
DM
391}
392
393sub save_rules {
394 my ($class, $param, $fw_conf, $rules) = @_;
395
1210ae94
DM
396 if (!defined($rules)) {
397 delete $fw_conf->{groups}->{$param->{group}};
398 } else {
399 $fw_conf->{groups}->{$param->{group}} = $rules;
400 }
401
86791289
DM
402 PVE::Firewall::save_clusterfw_conf($fw_conf);
403}
404
1210ae94
DM
405__PACKAGE__->register_method({
406 name => 'delete_security_group',
407 path => '',
408 method => 'DELETE',
409 description => "Delete security group.",
410 protected => 1,
2ba4951d
DM
411 permissions => {
412 check => ['perm', '/', [ 'Sys.Modify' ]],
413 },
1210ae94
DM
414 parameters => {
415 additionalProperties => 0,
75a12a9d 416 properties => {
1210ae94
DM
417 group => get_standard_option('pve-security-group-name'),
418 },
419 },
420 returns => { type => 'null' },
421 code => sub {
422 my ($param) = @_;
75a12a9d 423
1210ae94
DM
424 my (undef, $cluster_conf, $rules) = __PACKAGE__->load_config($param);
425
75a12a9d 426 die "Security group '$param->{group}' is not empty\n"
1210ae94
DM
427 if scalar(@$rules);
428
429 __PACKAGE__->save_rules($param, $cluster_conf, undef);
430
431 return undef;
432 }});
433
63c91681 434__PACKAGE__->register_handlers();
86791289
DM
435
436package PVE::API2::Firewall::ClusterRules;
437
438use strict;
439use warnings;
440
441use base qw(PVE::API2::Firewall::RulesBase);
442
b6b8e6ad
DM
443sub rule_env {
444 my ($class, $param) = @_;
75a12a9d 445
b6b8e6ad
DM
446 return 'cluster';
447}
448
86791289
DM
449sub load_config {
450 my ($class, $param) = @_;
451
452 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
453 my $rules = $fw_conf->{rules};
454
a523e057 455 return (undef, $fw_conf, $rules);
86791289
DM
456}
457
458sub 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
63c91681
DM
465__PACKAGE__->register_handlers();
466
467package PVE::API2::Firewall::HostRules;
468
469use strict;
470use warnings;
471use PVE::JSONSchema qw(get_standard_option);
472
473use base qw(PVE::API2::Firewall::RulesBase);
474
475__PACKAGE__->additional_parameters({ node => get_standard_option('pve-node')});
476
b6b8e6ad
DM
477sub rule_env {
478 my ($class, $param) = @_;
75a12a9d 479
b6b8e6ad
DM
480 return 'host';
481}
482
63c91681
DM
483sub load_config {
484 my ($class, $param) = @_;
485
a523e057
DM
486 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
487 my $fw_conf = PVE::Firewall::load_hostfw_conf($cluster_conf);
63c91681
DM
488 my $rules = $fw_conf->{rules};
489
a523e057 490 return ($cluster_conf, $fw_conf, $rules);
63c91681
DM
491}
492
493sub 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();
86791289 501
464f933e
DM
502package PVE::API2::Firewall::VMRules;
503
504use strict;
505use warnings;
506use PVE::JSONSchema qw(get_standard_option);
507
508use base qw(PVE::API2::Firewall::RulesBase);
509
75a12a9d 510__PACKAGE__->additional_parameters({
464f933e 511 node => get_standard_option('pve-node'),
75a12a9d 512 vmid => get_standard_option('pve-vmid'),
464f933e
DM
513});
514
b6b8e6ad
DM
515sub rule_env {
516 my ($class, $param) = @_;
75a12a9d 517
b6b8e6ad
DM
518 return 'vm';
519}
520
521sub load_config {
522 my ($class, $param) = @_;
523
a523e057
DM
524 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
525 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'vm', $param->{vmid});
b6b8e6ad
DM
526 my $rules = $fw_conf->{rules};
527
a523e057 528 return ($cluster_conf, $fw_conf, $rules);
b6b8e6ad
DM
529}
530
531sub 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
540package PVE::API2::Firewall::CTRules;
541
542use strict;
543use warnings;
544use PVE::JSONSchema qw(get_standard_option);
545
546use base qw(PVE::API2::Firewall::RulesBase);
547
75a12a9d 548__PACKAGE__->additional_parameters({
b6b8e6ad 549 node => get_standard_option('pve-node'),
75a12a9d 550 vmid => get_standard_option('pve-vmid'),
b6b8e6ad
DM
551});
552
553sub rule_env {
554 my ($class, $param) = @_;
75a12a9d 555
b6b8e6ad
DM
556 return 'ct';
557}
558
464f933e
DM
559sub load_config {
560 my ($class, $param) = @_;
561
a523e057
DM
562 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
563 my $fw_conf = PVE::Firewall::load_vmfw_conf($cluster_conf, 'ct', $param->{vmid});
464f933e
DM
564 my $rules = $fw_conf->{rules};
565
a523e057 566 return ($cluster_conf, $fw_conf, $rules);
464f933e
DM
567}
568
569sub 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
86791289 5781;