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