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