]> git.proxmox.com Git - pve-firewall.git/blob - src/PVE/API2/Firewall/IPSet.pm
generate_ipset: skip undefined ipsets
[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
213 foreach my $entry (@$ipset) {
214 if($entry->{cidr} eq $param->{cidr}) {
215 $entry->{nomatch} = $param->{nomatch};
216 $entry->{comment} = $param->{comment};
217 $class->save_ipset($param, $fw_conf, $ipset);
218 return;
219 }
220 }
221
222 raise_param_exc({ cidr => "no such IP/Network" });
223 }});
224 }
225
226 sub register_delete_ip {
227 my ($class) = @_;
228
229 my $properties = $class->additional_parameters();
230
231 $properties->{name} = $api_properties->{name};
232 $properties->{cidr} = $api_properties->{cidr};
233 $properties->{digest} = get_standard_option('pve-config-digest');
234
235 $class->register_method({
236 name => 'remove_ip',
237 path => '{cidr}',
238 method => 'DELETE',
239 description => "Remove IP or Network from IPSet.",
240 protected => 1,
241 parameters => {
242 additionalProperties => 0,
243 properties => $properties,
244 },
245 returns => { type => "null" },
246 code => sub {
247 my ($param) = @_;
248
249 my ($fw_conf, $ipset) = $class->load_config($param);
250
251 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
252 PVE::Tools::assert_if_modified($digest, $param->{digest});
253
254 my $new = [];
255
256 foreach my $entry (@$ipset) {
257 push @$new, $entry if $entry->{cidr} ne $param->{cidr};
258 }
259
260 $class->save_ipset($param, $fw_conf, $new);
261
262 return undef;
263 }});
264 }
265
266 sub register_handlers {
267 my ($class) = @_;
268
269 $class->register_get_ipset();
270 $class->register_create_ip();
271 $class->register_read_ip();
272 $class->register_update_ip();
273 $class->register_delete_ip();
274 }
275
276 package PVE::API2::Firewall::ClusterIPset;
277
278 use strict;
279 use warnings;
280
281 use base qw(PVE::API2::Firewall::IPSetBase);
282
283 sub load_config {
284 my ($class, $param) = @_;
285
286 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
287 my $ipset = $fw_conf->{ipset}->{$param->{name}};
288 die "no such IPSet '$param->{name}'\n" if !defined($ipset);
289
290 return ($fw_conf, $ipset);
291 }
292
293 sub save_ipset {
294 my ($class, $param, $fw_conf, $ipset) = @_;
295
296 $fw_conf->{ipset}->{$param->{name}} = $ipset;
297 PVE::Firewall::save_clusterfw_conf($fw_conf);
298 }
299
300 __PACKAGE__->register_handlers();
301
302 package PVE::API2::Firewall::BaseIPSetList;
303
304 use strict;
305 use warnings;
306 use PVE::JSONSchema qw(get_standard_option);
307 use PVE::Exception qw(raise_param_exc);
308 use PVE::Firewall;
309
310 use base qw(PVE::RESTHandler);
311
312 my $get_ipset_list = sub {
313 my ($fw_conf) = @_;
314
315 my $res = [];
316 foreach my $name (keys %{$fw_conf->{ipset}}) {
317 my $data = {
318 name => $name,
319 };
320 if (my $comment = $fw_conf->{ipset_comments}->{$name}) {
321 $data->{comment} = $comment;
322 }
323 push @$res, $data;
324 }
325
326 my ($list, $digest) = PVE::Firewall::copy_list_with_digest($res);
327
328 return wantarray ? ($list, $digest) : $list;
329 };
330
331 sub register_index {
332 my ($class) = @_;
333
334 $class->register_method({
335 name => 'ipset_index',
336 path => '',
337 method => 'GET',
338 description => "List IPSets",
339 parameters => {
340 additionalProperties => 0,
341 },
342 returns => {
343 type => 'array',
344 items => {
345 type => "object",
346 properties => {
347 name => get_standard_option('ipset-name'),
348 digest => get_standard_option('pve-config-digest', { optional => 0} ),
349 comment => {
350 type => 'string',
351 optional => 1,
352 }
353 },
354 },
355 links => [ { rel => 'child', href => "{name}" } ],
356 },
357 code => sub {
358 my ($param) = @_;
359
360 my $fw_conf = $class->load_config();
361
362 return &$get_ipset_list($fw_conf);
363 }});
364 }
365
366 sub register_create {
367 my ($class) = @_;
368
369 $class->register_method({
370 name => 'create_ipset',
371 path => '',
372 method => 'POST',
373 description => "Create new IPSet",
374 protected => 1,
375 parameters => {
376 additionalProperties => 0,
377 properties => {
378 name => get_standard_option('ipset-name'),
379 comment => {
380 type => 'string',
381 optional => 1,
382 },
383 rename => get_standard_option('ipset-name', {
384 description => "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
385 optional => 1,
386 }),
387 digest => get_standard_option('pve-config-digest'),
388 }
389 },
390 returns => { type => 'null' },
391 code => sub {
392 my ($param) = @_;
393
394 my $fw_conf = $class->load_config();
395
396 if ($param->{rename}) {
397 my (undef, $digest) = &$get_ipset_list($fw_conf);
398 PVE::Tools::assert_if_modified($digest, $param->{digest});
399
400 raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" })
401 if !$fw_conf->{ipset}->{$param->{rename}};
402
403 my $data = delete $fw_conf->{ipset}->{$param->{rename}};
404 $fw_conf->{ipset}->{$param->{name}} = $data;
405 if (my $comment = delete $fw_conf->{ipset_comments}->{$param->{rename}}) {
406 $fw_conf->{ipset_comments}->{$param->{name}} = $comment;
407 }
408 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
409 } else {
410 foreach my $name (keys %{$fw_conf->{ipset}}) {
411 raise_param_exc({ name => "IPSet '$name' already exists" })
412 if $name eq $param->{name};
413 }
414
415 $fw_conf->{ipset}->{$param->{name}} = [];
416 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
417 }
418
419 $class->save_config($fw_conf);
420
421 return undef;
422 }});
423 }
424
425 sub register_delete {
426 my ($class) = @_;
427
428 $class->register_method({
429 name => 'delete_ipset',
430 path => '{name}',
431 method => 'DELETE',
432 description => "Delete IPSet",
433 protected => 1,
434 parameters => {
435 additionalProperties => 0,
436 properties => {
437 name => get_standard_option('ipset-name'),
438 digest => get_standard_option('pve-config-digest'),
439 },
440 },
441 returns => { type => 'null' },
442 code => sub {
443 my ($param) = @_;
444
445 my $fw_conf = $class->load_config();
446
447 return undef if !$fw_conf->{ipset}->{$param->{name}};
448
449 my (undef, $digest) = &$get_ipset_list($fw_conf);
450 PVE::Tools::assert_if_modified($digest, $param->{digest});
451
452 die "IPSet '$param->{name}' is not empty\n"
453 if scalar(@{$fw_conf->{ipset}->{$param->{name}}});
454
455 delete $fw_conf->{ipset}->{$param->{name}};
456
457 $class->save_config($fw_conf);
458
459 return undef;
460 }});
461 }
462
463 sub register_handlers {
464 my ($class) = @_;
465
466 $class->register_index();
467 $class->register_create();
468 $class->register_delete();
469 }
470
471 package PVE::API2::Firewall::ClusterIPSetList;
472
473 use strict;
474 use warnings;
475 use PVE::Firewall;
476
477 use base qw(PVE::API2::Firewall::BaseIPSetList);
478
479 sub load_config {
480 my ($class) = @_;
481
482 return PVE::Firewall::load_clusterfw_conf();
483 }
484
485 sub save_config {
486 my ($class, $fw_conf) = @_;
487
488 PVE::Firewall::save_clusterfw_conf($fw_conf);
489 }
490
491 __PACKAGE__->register_handlers();
492
493 __PACKAGE__->register_method ({
494 subclass => "PVE::API2::Firewall::ClusterIPset",
495 path => '{name}',
496 # set fragment delimiter (no subdirs) - we need that, because CIDR address contain a slash '/'
497 fragmentDelimiter => '',
498 });
499
500 1;