c473f130894c505beb5487bba71fc9f755e3c1cd
[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                     digest => get_standard_option('pve-config-digest', { optional => 0} ),      
91                 },
92             },
93             links => [ { rel => 'child', href => "{cidr}" } ],
94         },
95         code => sub {
96             my ($param) = @_;
97
98             my ($fw_conf, $ipset) = $class->load_config($param);
99
100             return PVE::Firewall::copy_list_with_digest($ipset);
101         }});
102 }
103
104 sub register_create_ip {
105     my ($class) = @_;
106
107     my $properties = $class->additional_parameters();
108
109     $properties->{name} = $api_properties->{name};
110     $properties->{cidr} = $api_properties->{cidr};
111     $properties->{nomatch} = $api_properties->{nomatch};
112     $properties->{comment} = $api_properties->{comment};
113
114     $class->register_method({
115         name => 'create_ip',
116         path => '',
117         method => 'POST',
118         description => "Add IP or Network to IPSet.",
119         protected => 1,
120         parameters => {
121             additionalProperties => 0,
122             properties => $properties,
123         },
124         returns => { type => "null" },
125         code => sub {
126             my ($param) = @_;
127
128             my ($fw_conf, $ipset) = $class->load_config($param);
129
130             my $cidr = $param->{cidr};
131             
132             foreach my $entry (@$ipset) {
133                 raise_param_exc({ cidr => "address '$cidr' already exists" }) 
134                     if $entry->{cidr} eq $cidr;
135             }
136
137             my $data = { cidr => $cidr };
138             $data->{nomatch} = 1 if $param->{nomatch};
139             $data->{comment} = $param->{comment} if $param->{comment};
140
141             unshift @$ipset, $data;
142
143             $class->save_ipset($param, $fw_conf, $ipset);
144
145             return undef;
146         }});
147 }
148
149 sub register_read_ip {
150     my ($class) = @_;
151
152     my $properties = $class->additional_parameters();
153
154     $properties->{name} = $api_properties->{name};
155     $properties->{cidr} = $api_properties->{cidr};
156     
157     $class->register_method({
158         name => 'read_ip',
159         path => '{cidr}',
160         method => 'GET',
161         description => "Read IP or Network settings from IPSet.",
162         protected => 1,
163         parameters => {
164             additionalProperties => 0,
165             properties => $properties,
166         },
167         returns => { type => "object" },
168         code => sub {
169             my ($param) = @_;
170
171             my ($fw_conf, $ipset) = $class->load_config($param);
172
173             my $list = PVE::Firewall::copy_list_with_digest($ipset);
174
175             foreach my $entry (@$list) {
176                 if ($entry->{cidr} eq $param->{cidr}) {
177                     return $entry;
178                 }
179             }
180
181             raise_param_exc({ cidr => "no such IP/Network" });
182         }});
183 }
184
185 sub register_update_ip {
186     my ($class) = @_;
187
188     my $properties = $class->additional_parameters();
189
190     $properties->{name} = $api_properties->{name};
191     $properties->{cidr} = $api_properties->{cidr};
192     $properties->{nomatch} = $api_properties->{nomatch};
193     $properties->{comment} = $api_properties->{comment};
194     $properties->{digest} = get_standard_option('pve-config-digest');
195
196     $class->register_method({
197         name => 'update_ip',
198         path => '{cidr}',
199         method => 'PUT',
200         description => "Update IP or Network settings",
201         protected => 1,
202         parameters => {
203             additionalProperties => 0,
204             properties => $properties,
205         },
206         returns => { type => "null" },
207         code => sub {
208             my ($param) = @_;
209
210             my ($fw_conf, $ipset) = $class->load_config($param);
211
212             my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
213             PVE::Tools::assert_if_modified($digest, $param->{digest});
214             warn "TEST:$digest:$param->{digest}:\n";
215
216             foreach my $entry (@$ipset) {
217                 if($entry->{cidr} eq $param->{cidr}) {
218                     $entry->{nomatch} = $param->{nomatch};
219                     $entry->{comment} = $param->{comment};
220                     $class->save_ipset($param, $fw_conf, $ipset);
221                     return;
222                 }
223             }
224
225             raise_param_exc({ cidr => "no such IP/Network" });
226         }});
227 }
228
229 sub register_delete_ip {
230     my ($class) = @_;
231
232     my $properties = $class->additional_parameters();
233
234     $properties->{name} = $api_properties->{name};
235     $properties->{cidr} = $api_properties->{cidr};
236     $properties->{digest} = get_standard_option('pve-config-digest');
237
238     $class->register_method({
239         name => 'remove_ip',
240         path => '{cidr}',
241         method => 'DELETE',
242         description => "Remove IP or Network from IPSet.",
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, $ipset) = $class->load_config($param);
253
254             my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
255             PVE::Tools::assert_if_modified($digest, $param->{digest});
256
257             my $new = [];
258    
259             foreach my $entry (@$ipset) {
260                 push @$new, $entry if $entry->{cidr} ne $param->{cidr};
261             }
262
263             $class->save_ipset($param, $fw_conf, $new);
264             
265             return undef;
266         }});
267 }
268
269 sub register_handlers {
270     my ($class) = @_;
271
272     $class->register_get_ipset();
273     $class->register_create_ip();
274     $class->register_read_ip();
275     $class->register_update_ip();
276     $class->register_delete_ip();
277 }
278
279 package PVE::API2::Firewall::ClusterIPset;
280
281 use strict;
282 use warnings;
283
284 use base qw(PVE::API2::Firewall::IPSetBase);
285
286 sub load_config {
287     my ($class, $param) = @_;
288
289     my $fw_conf = PVE::Firewall::load_clusterfw_conf();
290     my $ipset = $fw_conf->{ipset}->{$param->{name}};
291     die "no such IPSet '$param->{name}'\n" if !defined($ipset);
292
293     return ($fw_conf, $ipset);
294 }
295
296 sub save_ipset {
297     my ($class, $param, $fw_conf, $ipset) = @_;
298
299     $fw_conf->{ipset}->{$param->{name}} = $ipset;
300     PVE::Firewall::save_clusterfw_conf($fw_conf);
301 }
302
303 __PACKAGE__->register_handlers();
304
305 package PVE::API2::Firewall::BaseIPSetList;
306
307 use strict;
308 use warnings;
309 use PVE::JSONSchema qw(get_standard_option);
310 use PVE::Exception qw(raise_param_exc);
311 use PVE::Firewall;
312
313 use base qw(PVE::RESTHandler);
314
315 my $get_ipset_list = sub {
316     my ($fw_conf) = @_;
317
318     my $res = [];
319     foreach my $name (keys %{$fw_conf->{ipset}}) {
320         my $data = { 
321             name => $name,
322         };
323         if (my $comment = $fw_conf->{ipset_comments}->{$name}) {
324             $data->{comment} = $comment;
325         }
326         push @$res, $data;
327     }
328
329     my ($list, $digest) = PVE::Firewall::copy_list_with_digest($res);
330
331     return wantarray ? ($list, $digest) : $list;
332 };
333
334 sub register_index {
335     my ($class) = @_;
336
337     $class->register_method({
338         name => 'ipset_index',
339         path => '',
340         method => 'GET',
341         description => "List IPSets",
342         parameters => {
343             additionalProperties => 0,
344         },
345         returns => {
346             type => 'array',
347             items => {
348                 type => "object",
349                 properties => { 
350                     name => get_standard_option('ipset-name'),
351                     digest => get_standard_option('pve-config-digest', { optional => 0} ),
352                     comment => { 
353                         type => 'string',
354                         optional => 1,
355                     }
356                 },
357             },
358             links => [ { rel => 'child', href => "{name}" } ],
359         },
360         code => sub {
361             my ($param) = @_;
362             
363             my $fw_conf = $class->load_config();
364
365             return &$get_ipset_list($fw_conf); 
366         }});
367 }
368
369 sub register_create {
370     my ($class) = @_;
371
372     $class->register_method({
373         name => 'create_ipset',
374         path => '',
375         method => 'POST',
376         description => "Create new IPSet",
377         protected => 1,
378         parameters => {
379             additionalProperties => 0,
380             properties => { 
381                 name => get_standard_option('ipset-name'),
382                 comment => {
383                     type => 'string',
384                     optional => 1,
385                 },
386                 rename => get_standard_option('ipset-name', {
387                     description => "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
388                     optional => 1,
389                 }),
390                 digest => get_standard_option('pve-config-digest'),
391             }
392         },
393         returns => { type => 'null' },
394         code => sub {
395             my ($param) = @_;
396             
397             my $fw_conf = $class->load_config();
398
399             if ($param->{rename}) {
400                 my (undef, $digest) = &$get_ipset_list($fw_conf);
401                 PVE::Tools::assert_if_modified($digest, $param->{digest});
402
403                 raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" }) 
404                     if !$fw_conf->{ipset}->{$param->{rename}};
405
406                 my $data = delete $fw_conf->{ipset}->{$param->{rename}};
407                 $fw_conf->{ipset}->{$param->{name}} = $data;
408                 if (my $comment = delete $fw_conf->{ipset_comments}->{$param->{rename}}) {
409                     $fw_conf->{ipset_comments}->{$param->{name}} = $comment;
410                 }
411                 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
412             } else { 
413                 foreach my $name (keys %{$fw_conf->{ipset}}) {
414                     raise_param_exc({ name => "IPSet '$name' already exists" }) 
415                         if $name eq $param->{name};
416                 }
417
418                 $fw_conf->{ipset}->{$param->{name}} = [];
419                 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
420             }
421
422             $class->save_config($fw_conf);
423
424             return undef;
425         }});
426 }
427
428 sub register_delete {
429     my ($class) = @_;
430
431     $class->register_method({
432         name => 'delete_ipset',
433         path => '{name}',
434         method => 'DELETE',
435         description => "Delete IPSet",
436         protected => 1,
437         parameters => {
438             additionalProperties => 0,
439             properties => { 
440                 name => get_standard_option('ipset-name'),
441                 digest => get_standard_option('pve-config-digest'),
442             },
443         },
444         returns => { type => 'null' },
445         code => sub {
446             my ($param) = @_;
447             
448             my $fw_conf = $class->load_config();
449
450             return undef if !$fw_conf->{ipset}->{$param->{name}};
451
452             my (undef, $digest) = &$get_ipset_list($fw_conf);
453             PVE::Tools::assert_if_modified($digest, $param->{digest});
454
455             die "IPSet '$param->{name}' is not empty\n" 
456                 if scalar(@{$fw_conf->{ipset}->{$param->{name}}});
457
458             delete $fw_conf->{ipset}->{$param->{name}};
459
460             $class->save_config($fw_conf);
461
462             return undef;
463         }});
464 }
465
466 sub register_handlers {
467     my ($class) = @_;
468
469     $class->register_index();
470     $class->register_create();
471     $class->register_delete();
472 }
473
474 package PVE::API2::Firewall::ClusterIPSetList;
475
476 use strict;
477 use warnings;
478 use PVE::Firewall;
479
480 use base qw(PVE::API2::Firewall::BaseIPSetList);
481
482 sub load_config {
483     my ($class) = @_;
484  
485     return PVE::Firewall::load_clusterfw_conf();
486 }
487
488 sub save_config {
489     my ($class, $fw_conf) = @_;
490
491     PVE::Firewall::save_clusterfw_conf($fw_conf);
492 }
493
494 __PACKAGE__->register_handlers();
495
496 __PACKAGE__->register_method ({
497     subclass => "PVE::API2::Firewall::ClusterIPset",  
498     path => '{name}',
499     # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/' 
500     fragmentDelimiter => '', 
501 });
502
503 1;