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