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