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