correctly verify ipset name
[pve-firewall.git] / src / PVE / API2 / Firewall / IPSet.pm
1 package PVE::API2::Firewall::IPSetBase;
2
3 use strict;
4 use warnings;
5 use PVE::Exception qw(raise raise_param_exc);
6 use PVE::JSONSchema qw(get_standard_option);
7
8 use PVE::Firewall;
9
10 use base qw(PVE::RESTHandler);
11
12 my $api_properties = { 
13     cidr => {
14         description => "Network/IP specification in CIDR format.",
15         type => 'string', format => 'IPv4orCIDR',
16     },
17     name => get_standard_option('ipset-name'),
18     comment => {
19         type => 'string',
20         optional => 1,
21     },
22     nomatch => {
23         type => 'boolean',
24         optional => 1,
25     },
26 };
27
28 sub load_config {
29     my ($class, $param) = @_;
30
31     die "implement this in subclass";
32
33     #return ($fw_conf, $rules);
34 }
35
36 sub save_rules {
37     my ($class, $param, $fw_conf, $rules) = @_;
38
39     die "implement this in subclass";
40 }
41
42 my $additional_param_hash = {};
43
44 sub additional_parameters {
45     my ($class, $new_value) = @_;
46
47     if (defined($new_value)) {
48         $additional_param_hash->{$class} = $new_value;
49     }
50
51     # return a copy
52     my $copy = {};
53     my $org = $additional_param_hash->{$class} || {};
54     foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
55     return $copy;
56 }
57
58 sub register_get_ipset {
59     my ($class) = @_;
60
61     my $properties = $class->additional_parameters();
62
63     $properties->{name} = $api_properties->{name};
64
65     $class->register_method({
66         name => 'get_ipset',
67         path => '',
68         method => 'GET',
69         description => "List IPSet content",
70         parameters => {
71             additionalProperties => 0,
72             properties => $properties,
73         },
74         returns => {
75             type => 'array',
76             items => {
77                 type => "object",
78                 properties => {
79                     cidr => {
80                         type => 'string',
81                     },
82                     comment => {
83                         type => 'string',
84                         optional => 1,
85                     },
86                     nomatch => {
87                         type => 'boolean',
88                         optional => 1,
89                     },                  
90                 },
91             },
92             links => [ { rel => 'child', href => "{cidr}" } ],
93         },
94         code => sub {
95             my ($param) = @_;
96
97             my ($fw_conf, $ipset) = $class->load_config($param);
98
99             return $ipset;
100         }});
101 }
102
103 sub register_create_ip {
104     my ($class) = @_;
105
106     my $properties = $class->additional_parameters();
107
108     $properties->{name} = $api_properties->{name};
109     $properties->{cidr} = $api_properties->{cidr};
110     $properties->{nomatch} = $api_properties->{nomatch};
111     $properties->{comment} = $api_properties->{comment};
112     
113     $class->register_method({
114         name => 'create_ip',
115         path => '',
116         method => 'POST',
117         description => "Add IP or Network to IPSet.",
118         protected => 1,
119         parameters => {
120             additionalProperties => 0,
121             properties => $properties,
122         },
123         returns => { type => "null" },
124         code => sub {
125             my ($param) = @_;
126
127             my ($fw_conf, $ipset) = $class->load_config($param);
128
129             my $cidr = $param->{cidr};
130             
131             foreach my $entry (@$ipset) {
132                 raise_param_exc({ cidr => "address '$cidr' already exists" }) 
133                     if $entry->{cidr} eq $cidr;
134             }
135
136             my $data = { cidr => $cidr };
137             $data->{nomatch} = 1 if $param->{nomatch};
138             $data->{comment} = $param->{comment} if $param->{comment};
139
140             unshift @$ipset, $data;
141
142             $class->save_ipset($param, $fw_conf, $ipset);
143
144             return undef;
145         }});
146 }
147
148 sub register_read_ip {
149     my ($class) = @_;
150
151     my $properties = $class->additional_parameters();
152
153     $properties->{name} = $api_properties->{name};
154     $properties->{cidr} = $api_properties->{cidr};
155     
156     $class->register_method({
157         name => 'read_ip',
158         path => '{cidr}',
159         method => 'GET',
160         description => "Read IP or Network settings from IPSet.",
161         protected => 1,
162         parameters => {
163             additionalProperties => 0,
164             properties => $properties,
165         },
166         returns => { type => "object" },
167         code => sub {
168             my ($param) = @_;
169
170             my ($fw_conf, $ipset) = $class->load_config($param);
171
172             foreach my $entry (@$ipset) {
173                 return $entry if $entry->{cidr} eq $param->{cidr};
174             }
175
176             raise_param_exc({ cidr => "no such IP/Network" });
177         }});
178 }
179
180 sub register_update_ip {
181     my ($class) = @_;
182
183     my $properties = $class->additional_parameters();
184
185     $properties->{name} = $api_properties->{name};
186     $properties->{cidr} = $api_properties->{cidr};
187     $properties->{nomatch} = $api_properties->{nomatch};
188     $properties->{comment} = $api_properties->{comment};
189     
190     $class->register_method({
191         name => 'update_ip',
192         path => '{cidr}',
193         method => 'PUT',
194         description => "Update IP or Network settings",
195         protected => 1,
196         parameters => {
197             additionalProperties => 0,
198             properties => $properties,
199         },
200         returns => { type => "null" },
201         code => sub {
202             my ($param) = @_;
203
204             my ($fw_conf, $ipset) = $class->load_config($param);
205
206             foreach my $entry (@$ipset) {
207                 if($entry->{cidr} eq $param->{cidr}) {
208                     $entry->{nomatch} = $param->{nomatch};
209                     $entry->{comment} = $param->{comment};
210                     $class->save_ipset($param, $fw_conf, $ipset);
211                     return;
212                 }
213             }
214
215             raise_param_exc({ cidr => "no such IP/Network" });
216         }});
217 }
218
219 sub register_delete_ip {
220     my ($class) = @_;
221
222     my $properties = $class->additional_parameters();
223
224     $properties->{name} = $api_properties->{name};
225     $properties->{cidr} = $api_properties->{cidr};
226     
227     $class->register_method({
228         name => 'remove_ip',
229         path => '{cidr}',
230         method => 'DELETE',
231         description => "Remove IP or Network from IPSet.",
232         protected => 1,
233         parameters => {
234             additionalProperties => 0,
235             properties => $properties,
236         },
237         returns => { type => "null" },
238         code => sub {
239             my ($param) = @_;
240
241             my ($fw_conf, $ipset) = $class->load_config($param);
242
243             my $new = [];
244    
245             foreach my $entry (@$ipset) {
246                 push @$new, $entry if $entry->{cidr} ne $param->{cidr};
247             }
248
249             $class->save_ipset($param, $fw_conf, $new);
250             
251             return undef;
252         }});
253 }
254
255 sub register_handlers {
256     my ($class) = @_;
257
258     $class->register_get_ipset();
259     $class->register_create_ip();
260     $class->register_read_ip();
261     $class->register_update_ip();
262     $class->register_delete_ip();
263 }
264
265 package PVE::API2::Firewall::ClusterIPset;
266
267 use strict;
268 use warnings;
269
270 use base qw(PVE::API2::Firewall::IPSetBase);
271
272 sub load_config {
273     my ($class, $param) = @_;
274
275     my $fw_conf = PVE::Firewall::load_clusterfw_conf();
276     my $ipset = $fw_conf->{ipset}->{$param->{name}};
277     die "no such IPSet '$param->{name}'\n" if !defined($ipset);
278
279     return ($fw_conf, $ipset);
280 }
281
282 sub save_ipset {
283     my ($class, $param, $fw_conf, $ipset) = @_;
284
285     $fw_conf->{ipset}->{$param->{name}} = $ipset;
286     PVE::Firewall::save_clusterfw_conf($fw_conf);
287 }
288
289 __PACKAGE__->register_handlers();
290
291 package PVE::API2::Firewall::BaseIPSetList;
292
293 use strict;
294 use warnings;
295 use PVE::JSONSchema qw(get_standard_option);
296 use PVE::Exception qw(raise_param_exc);
297 use PVE::Firewall;
298
299 use base qw(PVE::RESTHandler);
300
301 sub register_index {
302     my ($class) = @_;
303
304     $class->register_method({
305         name => 'ipset_index',
306         path => '',
307         method => 'GET',
308         description => "List IPSets",
309         parameters => {
310             additionalProperties => 0,
311         },
312         returns => {
313             type => 'array',
314             items => {
315                 type => "object",
316                 properties => { 
317                     name => get_standard_option('ipset-name'),
318                 },
319             },
320             links => [ { rel => 'child', href => "{name}" } ],
321         },
322         code => sub {
323             my ($param) = @_;
324             
325             my $fw_conf = $class->load_config();
326
327             my $res = [];
328             foreach my $name (keys %{$fw_conf->{ipset}}) {
329                 push @$res, { name => $name, count => scalar(@{$fw_conf->{ipset}->{$name}}) };
330             }
331
332             return $res;
333         }});
334 }
335
336 sub register_create {
337     my ($class) = @_;
338
339     $class->register_method({
340         name => 'create_ipset',
341         path => '',
342         method => 'POST',
343         description => "Create new IPSet",
344         protected => 1,
345         parameters => {
346             additionalProperties => 0,
347             properties => { 
348                 name => get_standard_option('ipset-name'),
349                 rename => get_standard_option('ipset-name', {
350                     description => "Rename an existing IPSet.",
351                     optional => 1,
352                 }),
353             }
354         },
355         returns => { type => 'null' },
356         code => sub {
357             my ($param) = @_;
358             
359             my $fw_conf = $class->load_config();
360
361             foreach my $name (keys %{$fw_conf->{ipset}}) {
362                 raise_param_exc({ name => "IPSet '$name' already exists" }) 
363                     if $name eq $param->{name};
364             }
365
366             if ($param->{rename}) {
367                 raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" }) 
368                     if !$fw_conf->{ipset}->{$param->{rename}};
369                 my $data = delete $fw_conf->{ipset}->{$param->{rename}};
370                 $fw_conf->{ipset}->{$param->{name}} = $data;
371             } else {
372                 $fw_conf->{ipset}->{$param->{name}} = [];
373             }
374
375             $class->save_config($fw_conf);
376
377             return undef;
378         }});
379 }
380
381 sub register_delete {
382     my ($class) = @_;
383
384     $class->register_method({
385         name => 'delete_ipset',
386         path => '{name}',
387         method => 'DELETE',
388         description => "Delete IPSet",
389         protected => 1,
390         parameters => {
391             additionalProperties => 0,
392             properties => { 
393                 name => get_standard_option('ipset-name'),
394             }
395         },
396         returns => { type => 'null' },
397         code => sub {
398             my ($param) = @_;
399             
400             my $fw_conf = $class->load_config();
401
402             return undef if !$fw_conf->{ipset}->{$param->{name}};
403
404             die "IPSet '$param->{name}' is not empty\n" 
405                 if scalar(@{$fw_conf->{ipset}->{$param->{name}}});
406
407             delete $fw_conf->{ipset}->{$param->{name}};
408
409             $class->save_config($fw_conf);
410
411             return undef;
412         }});
413 }
414
415 sub register_handlers {
416     my ($class) = @_;
417
418     $class->register_index();
419     $class->register_create();
420     $class->register_delete();
421 }
422
423 package PVE::API2::Firewall::ClusterIPSetList;
424
425 use strict;
426 use warnings;
427 use PVE::Firewall;
428
429 use base qw(PVE::API2::Firewall::BaseIPSetList);
430
431 sub load_config {
432     my ($class) = @_;
433  
434     return PVE::Firewall::load_clusterfw_conf();
435 }
436
437 sub save_config {
438     my ($class, $fw_conf) = @_;
439
440     PVE::Firewall::save_clusterfw_conf($fw_conf);
441 }
442
443 __PACKAGE__->register_handlers();
444
445 __PACKAGE__->register_method ({
446     subclass => "PVE::API2::Firewall::ClusterIPset",  
447     path => '{name}',
448     # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' 
449     fragmentDelimiter => '', 
450 });
451
452 1;