]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/API2/Firewall/IPSet.pm
rename save_rules to save_ipset
[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";
009ee3ac
DM
32}
33
9acc720f 34sub save_ipset {
009ee3ac
DM
35 my ($class, $param, $fw_conf, $rules) = @_;
36
37 die "implement this in subclass";
38}
39
40my $additional_param_hash = {};
41
42sub 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
56sub 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,
d72c631c
DM
87 },
88 digest => get_standard_option('pve-config-digest', { optional => 0} ),
009ee3ac
DM
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
5d38d64f 98 return PVE::Firewall::copy_list_with_digest($ipset);
009ee3ac
DM
99 }});
100}
101
a33c74f6 102sub register_create_ip {
009ee3ac
DM
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};
d72c631c 111
009ee3ac 112 $class->register_method({
a33c74f6 113 name => 'create_ip',
009ee3ac
DM
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
4a11bba5
DM
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 };
009ee3ac
DM
136 $data->{nomatch} = 1 if $param->{nomatch};
137 $data->{comment} = $param->{comment} if $param->{comment};
138
009ee3ac
DM
139 unshift @$ipset, $data;
140
141 $class->save_ipset($param, $fw_conf, $ipset);
142
143 return undef;
144 }});
145}
146
a33c74f6
DM
147sub 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
5d38d64f
DM
171 my $list = PVE::Firewall::copy_list_with_digest($ipset);
172
173 foreach my $entry (@$list) {
d72c631c 174 if ($entry->{cidr} eq $param->{cidr}) {
d72c631c
DM
175 return $entry;
176 }
a33c74f6
DM
177 }
178
179 raise_param_exc({ cidr => "no such IP/Network" });
180 }});
181}
182
183sub 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};
d72c631c
DM
192 $properties->{digest} = get_standard_option('pve-config-digest');
193
a33c74f6
DM
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
5d38d64f
DM
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";
d72c631c 213
a33c74f6
DM
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
227sub register_delete_ip {
009ee3ac
DM
228 my ($class) = @_;
229
230 my $properties = $class->additional_parameters();
231
232 $properties->{name} = $api_properties->{name};
233 $properties->{cidr} = $api_properties->{cidr};
d72c631c
DM
234 $properties->{digest} = get_standard_option('pve-config-digest');
235
009ee3ac
DM
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
5d38d64f
DM
252 my (undef, $digest) = PVE::Firewall::copy_list_with_digest($ipset);
253 PVE::Tools::assert_if_modified($digest, $param->{digest});
d72c631c 254
4a11bba5
DM
255 my $new = [];
256
257 foreach my $entry (@$ipset) {
258 push @$new, $entry if $entry->{cidr} ne $param->{cidr};
259 }
009ee3ac 260
4a11bba5
DM
261 $class->save_ipset($param, $fw_conf, $new);
262
009ee3ac
DM
263 return undef;
264 }});
265}
266
267sub register_handlers {
268 my ($class) = @_;
269
270 $class->register_get_ipset();
a33c74f6
DM
271 $class->register_create_ip();
272 $class->register_read_ip();
273 $class->register_update_ip();
274 $class->register_delete_ip();
009ee3ac
DM
275}
276
277package PVE::API2::Firewall::ClusterIPset;
278
279use strict;
280use warnings;
281
282use base qw(PVE::API2::Firewall::IPSetBase);
283
284sub 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
294sub 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
c85c87f9
DM
303package PVE::API2::Firewall::BaseIPSetList;
304
305use strict;
306use warnings;
e74a87f5 307use PVE::JSONSchema qw(get_standard_option);
c85c87f9 308use PVE::Exception qw(raise_param_exc);
e74a87f5 309use PVE::Firewall;
c85c87f9
DM
310
311use base qw(PVE::RESTHandler);
312
5d38d64f
DM
313my $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
c85c87f9
DM
332sub 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 => {
e74a87f5 348 name => get_standard_option('ipset-name'),
d72c631c
DM
349 digest => get_standard_option('pve-config-digest', { optional => 0} ),
350 comment => {
351 type => 'string',
352 optional => 1,
353 }
c85c87f9
DM
354 },
355 },
356 links => [ { rel => 'child', href => "{name}" } ],
357 },
358 code => sub {
359 my ($param) = @_;
360
361 my $fw_conf = $class->load_config();
362
5d38d64f 363 return &$get_ipset_list($fw_conf);
c85c87f9
DM
364 }});
365}
366
367sub 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 => {
e74a87f5 379 name => get_standard_option('ipset-name'),
d72c631c
DM
380 comment => {
381 type => 'string',
382 optional => 1,
383 },
e74a87f5 384 rename => get_standard_option('ipset-name', {
d72c631c 385 description => "Rename an existing IPSet. You can set 'rename' to the same value as 'name' to update the 'comment' of an existing IPSet.",
bc374ca7 386 optional => 1,
e74a87f5 387 }),
d72c631c 388 digest => get_standard_option('pve-config-digest'),
c85c87f9
DM
389 }
390 },
391 returns => { type => 'null' },
392 code => sub {
393 my ($param) = @_;
394
395 my $fw_conf = $class->load_config();
396
bc374ca7 397 if ($param->{rename}) {
5d38d64f
DM
398 my (undef, $digest) = &$get_ipset_list($fw_conf);
399 PVE::Tools::assert_if_modified($digest, $param->{digest});
400
bc374ca7
DM
401 raise_param_exc({ name => "IPSet '$param->{rename}' does not exists" })
402 if !$fw_conf->{ipset}->{$param->{rename}};
5d38d64f 403
bc374ca7
DM
404 my $data = delete $fw_conf->{ipset}->{$param->{rename}};
405 $fw_conf->{ipset}->{$param->{name}} = $data;
d72c631c
DM
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});
5d38d64f
DM
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
bc374ca7 416 $fw_conf->{ipset}->{$param->{name}} = [];
d72c631c 417 $fw_conf->{ipset_comments}->{$param->{name}} = $param->{comment} if defined($param->{comment});
bc374ca7
DM
418 }
419
c85c87f9
DM
420 $class->save_config($fw_conf);
421
422 return undef;
423 }});
424}
425
426sub 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 => {
e74a87f5 438 name => get_standard_option('ipset-name'),
d72c631c
DM
439 digest => get_standard_option('pve-config-digest'),
440 },
c85c87f9
DM
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
5d38d64f
DM
450 my (undef, $digest) = &$get_ipset_list($fw_conf);
451 PVE::Tools::assert_if_modified($digest, $param->{digest});
452
76aad6c5 453 die "IPSet '$param->{name}' is not empty\n"
c85c87f9
DM
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
464sub register_handlers {
465 my ($class) = @_;
466
467 $class->register_index();
468 $class->register_create();
469 $class->register_delete();
470}
471
472package PVE::API2::Firewall::ClusterIPSetList;
473
474use strict;
475use warnings;
476use PVE::Firewall;
477
478use base qw(PVE::API2::Firewall::BaseIPSetList);
479
480sub load_config {
481 my ($class) = @_;
482
483 return PVE::Firewall::load_clusterfw_conf();
484}
485
486sub 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
009ee3ac 5011;