]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/API2/Firewall/IPSet.pm
improve concurrent update handling
[pve-firewall.git] / src / PVE / API2 / Firewall / IPSet.pm
CommitLineData
009ee3ac
DM
1package PVE::API2::Firewall::IPSetBase;
2
3use strict;
4use warnings;
4a11bba5 5use PVE::Exception qw(raise raise_param_exc);
009ee3ac
DM
6use PVE::JSONSchema qw(get_standard_option);
7
8use PVE::Firewall;
9
10use base qw(PVE::RESTHandler);
11
12my $api_properties = {
13 cidr => {
14 description => "Network/IP specification in CIDR format.",
15 type => 'string', format => 'IPv4orCIDR',
16 },
e74a87f5 17 name => get_standard_option('ipset-name'),
009ee3ac
DM
18 comment => {
19 type => 'string',
20 optional => 1,
21 },
22 nomatch => {
23 type => 'boolean',
24 optional => 1,
25 },
26};
27
28sub load_config {
29 my ($class, $param) = @_;
30
31 die "implement this in subclass";
32
33 #return ($fw_conf, $rules);
34}
35
36sub save_rules {
37 my ($class, $param, $fw_conf, $rules) = @_;
38
39 die "implement this in subclass";
40}
41
42my $additional_param_hash = {};
43
44sub 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
58sub 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,
d72c631c
DM
89 },
90 digest => get_standard_option('pve-config-digest', { optional => 0} ),
009ee3ac
DM
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
5d38d64f 100 return PVE::Firewall::copy_list_with_digest($ipset);
009ee3ac
DM
101 }});
102}
103
a33c74f6 104sub register_create_ip {
009ee3ac
DM
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};
d72c631c 113
009ee3ac 114 $class->register_method({
a33c74f6 115 name => 'create_ip',
009ee3ac
DM
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
4a11bba5
DM
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 };
009ee3ac
DM
138 $data->{nomatch} = 1 if $param->{nomatch};
139 $data->{comment} = $param->{comment} if $param->{comment};
140
009ee3ac
DM
141 unshift @$ipset, $data;
142
143 $class->save_ipset($param, $fw_conf, $ipset);
144
145 return undef;
146 }});
147}
148
a33c74f6
DM
149sub 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
5d38d64f
DM
173 my $list = PVE::Firewall::copy_list_with_digest($ipset);
174
175 foreach my $entry (@$list) {
d72c631c 176 if ($entry->{cidr} eq $param->{cidr}) {
d72c631c
DM
177 return $entry;
178 }
a33c74f6
DM
179 }
180
181 raise_param_exc({ cidr => "no such IP/Network" });
182 }});
183}
184
185sub 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};
d72c631c
DM
194 $properties->{digest} = get_standard_option('pve-config-digest');
195
a33c74f6
DM
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
5d38d64f
DM
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";
d72c631c 215
a33c74f6
DM
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
229sub register_delete_ip {
009ee3ac
DM
230 my ($class) = @_;
231
232 my $properties = $class->additional_parameters();
233
234 $properties->{name} = $api_properties->{name};
235 $properties->{cidr} = $api_properties->{cidr};
d72c631c
DM
236 $properties->{digest} = get_standard_option('pve-config-digest');
237
009ee3ac
DM
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
5d38d64f
DM
254 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
255 PVE::Tools::assert_if_modified($digest, $param->{digest});
d72c631c 256
4a11bba5
DM
257 my $new = [];
258
259 foreach my $entry (@$ipset) {
260 push @$new, $entry if $entry->{cidr} ne $param->{cidr};
261 }
009ee3ac 262
4a11bba5
DM
263 $class->save_ipset($param, $fw_conf, $new);
264
009ee3ac
DM
265 return undef;
266 }});
267}
268
269sub register_handlers {
270 my ($class) = @_;
271
272 $class->register_get_ipset();
a33c74f6
DM
273 $class->register_create_ip();
274 $class->register_read_ip();
275 $class->register_update_ip();
276 $class->register_delete_ip();
009ee3ac
DM
277}
278
279package PVE::API2::Firewall::ClusterIPset;
280
281use strict;
282use warnings;
283
284use base qw(PVE::API2::Firewall::IPSetBase);
285
286sub 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
296sub 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
c85c87f9
DM
305package PVE::API2::Firewall::BaseIPSetList;
306
307use strict;
308use warnings;
e74a87f5 309use PVE::JSONSchema qw(get_standard_option);
c85c87f9 310use PVE::Exception qw(raise_param_exc);
e74a87f5 311use PVE::Firewall;
c85c87f9
DM
312
313use base qw(PVE::RESTHandler);
314
5d38d64f
DM
315my $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
c85c87f9
DM
334sub 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 => {
e74a87f5 350 name => get_standard_option('ipset-name'),
d72c631c
DM
351 digest => get_standard_option('pve-config-digest', { optional => 0} ),
352 comment => {
353 type => 'string',
354 optional => 1,
355 }
c85c87f9
DM
356 },
357 },
358 links => [ { rel => 'child', href => "{name}" } ],
359 },
360 code => sub {
361 my ($param) = @_;
362
363 my $fw_conf = $class->load_config();
364
5d38d64f 365 return &$get_ipset_list($fw_conf);
c85c87f9
DM
366 }});
367}
368
369sub 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 => {
e74a87f5 381 name => get_standard_option('ipset-name'),
d72c631c
DM
382 comment => {
383 type => 'string',
384 optional => 1,
385 },
e74a87f5 386 rename => get_standard_option('ipset-name', {
d72c631c 387 description => "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
bc374ca7 388 optional => 1,
e74a87f5 389 }),
d72c631c 390 digest => get_standard_option('pve-config-digest'),
c85c87f9
DM
391 }
392 },
393 returns => { type => 'null' },
394 code => sub {
395 my ($param) = @_;
396
397 my $fw_conf = $class->load_config();
398
bc374ca7 399 if ($param->{rename}) {
5d38d64f
DM
400 my (undef, $digest) = &$get_ipset_list($fw_conf);
401 PVE::Tools::assert_if_modified($digest, $param->{digest});
402
bc374ca7
DM
403 raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" })
404 if !$fw_conf->{ipset}->{$param->{rename}};
5d38d64f 405
bc374ca7
DM
406 my $data = delete $fw_conf->{ipset}->{$param->{rename}};
407 $fw_conf->{ipset}->{$param->{name}} = $data;
d72c631c
DM
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});
5d38d64f
DM
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
bc374ca7 418 $fw_conf->{ipset}->{$param->{name}} = [];
d72c631c 419 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
bc374ca7
DM
420 }
421
c85c87f9
DM
422 $class->save_config($fw_conf);
423
424 return undef;
425 }});
426}
427
428sub 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 => {
e74a87f5 440 name => get_standard_option('ipset-name'),
d72c631c
DM
441 digest => get_standard_option('pve-config-digest'),
442 },
c85c87f9
DM
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
5d38d64f
DM
452 my (undef, $digest) = &$get_ipset_list($fw_conf);
453 PVE::Tools::assert_if_modified($digest, $param->{digest});
454
76aad6c5 455 die "IPSet '$param->{name}' is not empty\n"
c85c87f9
DM
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
466sub register_handlers {
467 my ($class) = @_;
468
469 $class->register_index();
470 $class->register_create();
471 $class->register_delete();
472}
473
474package PVE::API2::Firewall::ClusterIPSetList;
475
476use strict;
477use warnings;
478use PVE::Firewall;
479
480use base qw(PVE::API2::Firewall::BaseIPSetList);
481
482sub load_config {
483 my ($class) = @_;
484
485 return PVE::Firewall::load_clusterfw_conf();
486}
487
488sub 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
009ee3ac 5031;