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