improve rules API
[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 ($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 allow_groups {
37     return 1;
38 }
39
40 sub additional_parameters {
41     my ($class, $new_value) = @_;
42
43     if (defined($new_value)) {
44         $additional_param_hash->{$class} = $new_value;
45     }
46
47     # return a copy
48     my $copy = {};
49     my $org = $additional_param_hash->{$class} || {};
50     foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
51     return $copy;
52 }
53
54 sub register_get_rules {
55     my ($class) = @_;
56
57     my $properties = $class->additional_parameters();
58
59     $class->register_method({
60         name => 'get_rules',
61         path => '',
62         method => 'GET',
63         description => "List rules.",
64         parameters => {
65             additionalProperties => 0,
66             properties => $properties,
67         },
68         returns => {
69             type => 'array',
70             items => {
71                 type => "object",
72                 properties => {
73                     pos => {
74                         type => 'integer',
75                     }
76                 },
77             },
78             links => [ { rel => 'child', href => "{pos}" } ],
79         },
80         code => sub {
81             my ($param) = @_;
82
83             my ($fw_conf, $rules) = $class->load_config($param);
84
85             my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules);
86
87             my $ind = 0;
88             foreach my $rule (@$list) {
89                 $rule->{pos} = $ind++;
90             }
91
92             return $list;
93         }});
94 }
95
96 sub register_get_rule {
97     my ($class) = @_;
98
99     my $properties = $class->additional_parameters();
100
101     $properties->{pos} = $api_properties->{pos};
102     
103     $class->register_method({
104         name => 'get_rule',
105         path => '{pos}',
106         method => 'GET',
107         description => "Get single rule data.",
108         parameters => {
109             additionalProperties => 0,
110             properties => $properties,
111         },
112         returns => {
113             type => "object",
114             properties => {
115                 pos => {
116                     type => 'integer',
117                 }
118             },
119         },
120         code => sub {
121             my ($param) = @_;
122
123             my ($fw_conf, $rules) = $class->load_config($param);
124
125             my ($list, $digest) = PVE::Firewall::copy_list_with_digest($rules);
126         
127             die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$list);
128         
129             my $rule = $list->[$param->{pos}];
130             $rule->{pos} = $param->{pos};
131
132             return $rule;
133         }});
134 }
135
136 sub register_create_rule {
137     my ($class) = @_;
138
139     my $properties = $class->additional_parameters();
140
141     my $create_rule_properties = PVE::Firewall::add_rule_properties($properties);
142     $create_rule_properties->{action}->{optional} = 0;
143     $create_rule_properties->{type}->{optional} = 0;
144     
145     $class->register_method({
146         name => 'create_rule',
147         path => '',
148         method => 'POST',
149         description => "Create new rule.",
150         protected => 1,
151         parameters => {
152             additionalProperties => 0,
153             properties => $create_rule_properties,
154         },
155         returns => { type => "null" },
156         code => sub {
157             my ($param) = @_;
158
159             my ($fw_conf, $rules) = $class->load_config($param);
160
161             my $rule = {};
162
163             PVE::Firewall::copy_rule_data($rule, $param);
164             PVE::Firewall::verify_rule($rule, $class->allow_groups());
165
166             $rule->{enable} = 0 if !defined($param->{enable});
167
168             unshift @$rules, $rule;
169
170             $class->save_rules($param, $fw_conf, $rules);
171
172             return undef;
173         }});
174 }
175
176 sub register_update_rule {
177     my ($class) = @_;
178
179     my $properties = $class->additional_parameters();
180
181     $properties->{pos} = $api_properties->{pos};
182     
183     $properties->{moveto} = {
184         description => "Move rule to new position <moveto>. Other arguments are ignored.",
185         type => 'integer',
186         minimum => 0,
187         optional => 1,
188     };
189
190     $properties->{delete} = {
191         type => 'string', format => 'pve-configid-list',
192         description => "A list of settings you want to delete.",
193         optional => 1,
194     };
195
196     my $update_rule_properties = PVE::Firewall::add_rule_properties($properties);
197
198     $class->register_method({
199         name => 'update_rule',
200         path => '{pos}',
201         method => 'PUT',
202         description => "Modify rule data.",
203         protected => 1,
204         parameters => {
205             additionalProperties => 0,
206             properties => $update_rule_properties,
207         },
208         returns => { type => "null" },
209         code => sub {
210             my ($param) = @_;
211
212             my ($fw_conf, $rules) = $class->load_config($param);
213
214             my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
215             PVE::Tools::assert_if_modified($digest, $param->{digest});
216
217             die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
218         
219             my $rule = $rules->[$param->{pos}];
220
221             my $moveto = $param->{moveto};
222             if (defined($moveto) && $moveto != $param->{pos}) {
223                 my $newrules = [];
224                 for (my $i = 0; $i < scalar(@$rules); $i++) {
225                     next if $i == $param->{pos};
226                     if ($i == $moveto) {
227                         push @$newrules, $rule;
228                     }
229                     push @$newrules, $rules->[$i];
230                 }
231                 push @$newrules, $rule if $moveto >= scalar(@$rules);
232                 $rules = $newrules;
233             } else {
234                 PVE::Firewall::copy_rule_data($rule, $param);
235                 
236                 PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'};
237
238                 PVE::Firewall::verify_rule($rule, $class->allow_groups());
239             }
240
241             $class->save_rules($param, $fw_conf, $rules);
242
243             return undef;
244         }});
245 }
246
247 sub register_delete_rule {
248     my ($class) = @_;
249
250     my $properties = $class->additional_parameters();
251
252     $properties->{pos} = $api_properties->{pos};
253
254     $properties->{digest} = get_standard_option('pve-config-digest');
255     
256     $class->register_method({
257         name => 'delete_rule',
258         path => '{pos}',
259         method => 'DELETE',
260         description => "Delete rule.",
261         protected => 1,
262         parameters => {
263             additionalProperties => 0,
264             properties => $properties,
265         },
266         returns => { type => "null" },
267         code => sub {
268             my ($param) = @_;
269
270             my ($fw_conf, $rules) = $class->load_config($param);
271
272             my (undef, $digest) = PVE::Firewall::copy_list_with_digest($rules);
273             PVE::Tools::assert_if_modified($digest, $param->{digest});
274         
275             die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
276         
277             splice(@$rules, $param->{pos}, 1);
278             
279             $class->save_rules($param, $fw_conf, $rules);
280
281             return undef;
282         }});
283 }
284
285 sub register_handlers {
286     my ($class) = @_;
287
288     $class->register_get_rules();
289     $class->register_get_rule();
290     $class->register_create_rule();
291     $class->register_update_rule();
292     $class->register_delete_rule();
293 }
294
295 package PVE::API2::Firewall::GroupRules;
296
297 use strict;
298 use warnings;
299 use PVE::JSONSchema qw(get_standard_option);
300
301 use base qw(PVE::API2::Firewall::RulesBase);
302
303 __PACKAGE__->additional_parameters({ group => get_standard_option('pve-security-group-name') });
304
305 sub allow_groups {
306     return 0;
307 }
308
309 sub load_config {
310     my ($class, $param) = @_;
311
312     my $fw_conf = PVE::Firewall::load_clusterfw_conf();
313     my $rules = $fw_conf->{groups}->{$param->{group}};
314     die "no such security group '$param->{group}'\n" if !defined($rules);
315
316     return ($fw_conf, $rules);
317 }
318
319 sub save_rules {
320     my ($class, $param, $fw_conf, $rules) = @_;
321
322     $fw_conf->{groups}->{$param->{group}} = $rules;
323     PVE::Firewall::save_clusterfw_conf($fw_conf);
324 }
325
326 __PACKAGE__->register_handlers();
327
328 package PVE::API2::Firewall::ClusterRules;
329
330 use strict;
331 use warnings;
332
333 use base qw(PVE::API2::Firewall::RulesBase);
334
335 sub load_config {
336     my ($class, $param) = @_;
337
338     my $fw_conf = PVE::Firewall::load_clusterfw_conf();
339     my $rules = $fw_conf->{rules};
340
341     return ($fw_conf, $rules);
342 }
343
344 sub save_rules {
345     my ($class, $param, $fw_conf, $rules) = @_;
346
347     $fw_conf->{rules} = $rules;
348     PVE::Firewall::save_clusterfw_conf($fw_conf);
349 }
350
351 __PACKAGE__->register_handlers();
352
353 package PVE::API2::Firewall::HostRules;
354
355 use strict;
356 use warnings;
357 use PVE::JSONSchema qw(get_standard_option);
358
359 use base qw(PVE::API2::Firewall::RulesBase);
360
361 __PACKAGE__->additional_parameters({ node => get_standard_option('pve-node')});
362
363 sub load_config {
364     my ($class, $param) = @_;
365
366     my $fw_conf = PVE::Firewall::load_hostfw_conf();
367     my $rules = $fw_conf->{rules};
368
369     return ($fw_conf, $rules);
370 }
371
372 sub save_rules {
373     my ($class, $param, $fw_conf, $rules) = @_;
374
375     $fw_conf->{rules} = $rules;
376     PVE::Firewall::save_hostfw_conf($fw_conf);
377 }
378
379 __PACKAGE__->register_handlers();
380
381 package PVE::API2::Firewall::VMRules;
382
383 use strict;
384 use warnings;
385 use PVE::JSONSchema qw(get_standard_option);
386
387 use base qw(PVE::API2::Firewall::RulesBase);
388
389 __PACKAGE__->additional_parameters({ 
390     node => get_standard_option('pve-node'),
391     vmid => get_standard_option('pve-vmid'),                               
392 });
393
394 sub load_config {
395     my ($class, $param) = @_;
396
397     my $fw_conf = PVE::Firewall::load_vmfw_conf($param->{vmid});
398     my $rules = $fw_conf->{rules};
399
400     return ($fw_conf, $rules);
401 }
402
403 sub save_rules {
404     my ($class, $param, $fw_conf, $rules) = @_;
405
406     $fw_conf->{rules} = $rules;
407     PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
408 }
409
410 __PACKAGE__->register_handlers();
411
412 1;