]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/API2/Firewall/Rules.pm
security group API: protect against concurrent updates
[pve-firewall.git] / src / PVE / API2 / Firewall / Rules.pm
CommitLineData
86791289
DM
1package PVE::API2::Firewall::RulesBase;
2
3use strict;
4use warnings;
5use PVE::JSONSchema qw(get_standard_option);
0d22acb3 6use PVE::Exception qw(raise raise_param_exc);
86791289
DM
7
8use PVE::Firewall;
9
10use base qw(PVE::RESTHandler);
11
12my $api_properties = {
86791289
DM
13 pos => {
14 description => "Rule position.",
15 type => 'integer',
16 minimum => 0,
17 },
18};
19
20sub load_config {
21 my ($class, $param) = @_;
22
23 die "implement this in subclass";
24
25 #return ($fw_conf, $rules);
26}
27
28sub save_rules {
29 my ($class, $param, $fw_conf, $rules) = @_;
30
31 die "implement this in subclass";
32}
33
63c91681 34my $additional_param_hash = {};
86791289 35
7ca36671
DM
36sub allow_groups {
37 return 1;
38}
39
63c91681 40sub additional_parameters {
86791289
DM
41 my ($class, $new_value) = @_;
42
63c91681
DM
43 if (defined($new_value)) {
44 $additional_param_hash->{$class} = $new_value;
45 }
86791289 46
63c91681
DM
47 # return a copy
48 my $copy = {};
49 my $org = $additional_param_hash->{$class} || {};
50 foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; }
51 return $copy;
86791289
DM
52}
53
54sub register_get_rules {
55 my ($class) = @_;
56
63c91681 57 my $properties = $class->additional_parameters();
86791289
DM
58
59 $class->register_method({
60 name => 'get_rules',
61 path => '',
62 method => 'GET',
63 description => "List rules.",
64 parameters => {
65 additionalProperties => 0,
66 properties => $properties,
67 },
68 returns => {
69 type => 'array',
70 items => {
71 type => "object",
72 properties => {
73 pos => {
74 type => 'integer',
75 }
76 },
77 },
78 links => [ { rel => 'child', href => "{pos}" } ],
79 },
80 code => sub {
81 my ($param) = @_;
82
83 my ($fw_conf, $rules) = $class->load_config($param);
84
85 my $digest = $fw_conf->{digest};
86
87 my $res = [];
88
89 my $ind = 0;
90 foreach my $rule (@$rules) {
91 push @$res, PVE::Firewall::cleanup_fw_rule($rule, $digest, $ind++);
92 }
93
94 return $res;
95 }});
96}
97
98sub register_get_rule {
99 my ($class) = @_;
100
63c91681 101 my $properties = $class->additional_parameters();
86791289
DM
102
103 $properties->{pos} = $api_properties->{pos};
104
86791289
DM
105 $class->register_method({
106 name => 'get_rule',
107 path => '{pos}',
108 method => 'GET',
109 description => "Get single rule data.",
110 parameters => {
111 additionalProperties => 0,
112 properties => $properties,
113 },
114 returns => {
115 type => "object",
116 properties => {
117 pos => {
118 type => 'integer',
119 }
120 },
121 },
122 code => sub {
123 my ($param) = @_;
124
125 my ($fw_conf, $rules) = $class->load_config($param);
126
127 my $digest = $fw_conf->{digest};
128 # fixme: check digest
129
130 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
131
132 my $rule = $rules->[$param->{pos}];
133
134 return PVE::Firewall::cleanup_fw_rule($rule, $digest, $param->{pos});
135 }});
136}
137
138sub register_create_rule {
139 my ($class) = @_;
140
63c91681 141 my $properties = $class->additional_parameters();
86791289
DM
142
143 my $create_rule_properties = PVE::Firewall::add_rule_properties($properties);
3655b01f
DM
144 $create_rule_properties->{action}->{optional} = 0;
145 $create_rule_properties->{type}->{optional} = 0;
146
86791289
DM
147 $class->register_method({
148 name => 'create_rule',
149 path => '',
150 method => 'POST',
151 description => "Create new rule.",
152 protected => 1,
153 parameters => {
154 additionalProperties => 0,
155 properties => $create_rule_properties,
156 },
157 returns => { type => "null" },
158 code => sub {
159 my ($param) = @_;
160
161 my ($fw_conf, $rules) = $class->load_config($param);
162
163 my $digest = $fw_conf->{digest};
3655b01f
DM
164
165 my $rule = {};
86791289
DM
166
167 PVE::Firewall::copy_rule_data($rule, $param);
7ca36671 168 PVE::Firewall::verify_rule($rule, $class->allow_groups());
86791289 169
3655b01f
DM
170 $rule->{enable} = 0 if !defined($param->{enable});
171
86791289
DM
172 unshift @$rules, $rule;
173
174 $class->save_rules($param, $fw_conf, $rules);
175
176 return undef;
177 }});
178}
179
180sub register_update_rule {
181 my ($class) = @_;
182
63c91681 183 my $properties = $class->additional_parameters();
86791289
DM
184
185 $properties->{pos} = $api_properties->{pos};
186
86791289
DM
187 $properties->{moveto} = {
188 description => "Move rule to new position <moveto>. Other arguments are ignored.",
189 type => 'integer',
190 minimum => 0,
191 optional => 1,
192 };
193
5b7974df
DM
194 $properties->{delete} = {
195 type => 'string', format => 'pve-configid-list',
196 description => "A list of settings you want to delete.",
197 optional => 1,
198 };
199
86791289
DM
200 my $update_rule_properties = PVE::Firewall::add_rule_properties($properties);
201
202 $class->register_method({
203 name => 'update_rule',
204 path => '{pos}',
205 method => 'PUT',
206 description => "Modify rule data.",
207 protected => 1,
208 parameters => {
209 additionalProperties => 0,
210 properties => $update_rule_properties,
211 },
212 returns => { type => "null" },
213 code => sub {
214 my ($param) = @_;
215
216 my ($fw_conf, $rules) = $class->load_config($param);
217
218 my $digest = $fw_conf->{digest};
219 # fixme: check digest
220
221 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
222
223 my $rule = $rules->[$param->{pos}];
224
225 my $moveto = $param->{moveto};
226 if (defined($moveto) && $moveto != $param->{pos}) {
227 my $newrules = [];
228 for (my $i = 0; $i < scalar(@$rules); $i++) {
229 next if $i == $param->{pos};
230 if ($i == $moveto) {
231 push @$newrules, $rule;
232 }
233 push @$newrules, $rules->[$i];
234 }
235 push @$newrules, $rule if $moveto >= scalar(@$rules);
236 $rules = $newrules;
237 } else {
3655b01f
DM
238 raise_param_exc({ type => "property is missing"})
239 if !defined($param->{type});
240 raise_param_exc({ action => "property is missing"})
241 if !defined($param->{action});
242
86791289 243 PVE::Firewall::copy_rule_data($rule, $param);
5b7974df
DM
244
245 PVE::Firewall::delete_rule_properties($rule, $param->{'delete'}) if $param->{'delete'};
7ca36671
DM
246
247 PVE::Firewall::verify_rule($rule, $class->allow_groups());
86791289
DM
248 }
249
250 $class->save_rules($param, $fw_conf, $rules);
251
252 return undef;
253 }});
254}
255
256sub register_delete_rule {
257 my ($class) = @_;
258
63c91681 259 my $properties = $class->additional_parameters();
86791289
DM
260
261 $properties->{pos} = $api_properties->{pos};
262
86791289
DM
263 $class->register_method({
264 name => 'delete_rule',
265 path => '{pos}',
266 method => 'DELETE',
267 description => "Delete rule.",
268 protected => 1,
269 parameters => {
270 additionalProperties => 0,
271 properties => $properties,
272 },
273 returns => { type => "null" },
274 code => sub {
275 my ($param) = @_;
276
277 my ($fw_conf, $rules) = $class->load_config($param);
278
279 my $digest = $fw_conf->{digest};
280 # fixme: check digest
281
282 die "no rule at position $param->{pos}\n" if $param->{pos} >= scalar(@$rules);
283
284 splice(@$rules, $param->{pos}, 1);
285
286 $class->save_rules($param, $fw_conf, $rules);
287
288 return undef;
289 }});
290}
291
292sub register_handlers {
293 my ($class) = @_;
294
295 $class->register_get_rules();
296 $class->register_get_rule();
297 $class->register_create_rule();
298 $class->register_update_rule();
299 $class->register_delete_rule();
300}
301
302package PVE::API2::Firewall::GroupRules;
303
304use strict;
305use warnings;
387d0ffc 306use PVE::JSONSchema qw(get_standard_option);
86791289
DM
307
308use base qw(PVE::API2::Firewall::RulesBase);
309
387d0ffc 310__PACKAGE__->additional_parameters({ group => get_standard_option('pve-security-group-name') });
86791289 311
7ca36671
DM
312sub allow_groups {
313 return 0;
314}
315
86791289
DM
316sub load_config {
317 my ($class, $param) = @_;
318
319 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
320 my $rules = $fw_conf->{groups}->{$param->{group}};
321 die "no such security group '$param->{group}'\n" if !defined($rules);
322
323 return ($fw_conf, $rules);
324}
325
326sub save_rules {
327 my ($class, $param, $fw_conf, $rules) = @_;
328
329 $fw_conf->{groups}->{$param->{group}} = $rules;
330 PVE::Firewall::save_clusterfw_conf($fw_conf);
331}
332
63c91681 333__PACKAGE__->register_handlers();
86791289
DM
334
335package PVE::API2::Firewall::ClusterRules;
336
337use strict;
338use warnings;
339
340use base qw(PVE::API2::Firewall::RulesBase);
341
342sub load_config {
343 my ($class, $param) = @_;
344
345 my $fw_conf = PVE::Firewall::load_clusterfw_conf();
346 my $rules = $fw_conf->{rules};
347
348 return ($fw_conf, $rules);
349}
350
351sub save_rules {
352 my ($class, $param, $fw_conf, $rules) = @_;
353
354 $fw_conf->{rules} = $rules;
355 PVE::Firewall::save_clusterfw_conf($fw_conf);
356}
357
63c91681
DM
358__PACKAGE__->register_handlers();
359
360package PVE::API2::Firewall::HostRules;
361
362use strict;
363use warnings;
364use PVE::JSONSchema qw(get_standard_option);
365
366use base qw(PVE::API2::Firewall::RulesBase);
367
368__PACKAGE__->additional_parameters({ node => get_standard_option('pve-node')});
369
370sub load_config {
371 my ($class, $param) = @_;
372
373 my $fw_conf = PVE::Firewall::load_hostfw_conf();
374 my $rules = $fw_conf->{rules};
375
376 return ($fw_conf, $rules);
377}
378
379sub save_rules {
380 my ($class, $param, $fw_conf, $rules) = @_;
381
382 $fw_conf->{rules} = $rules;
383 PVE::Firewall::save_hostfw_conf($fw_conf);
384}
385
386__PACKAGE__->register_handlers();
86791289 387
464f933e
DM
388package PVE::API2::Firewall::VMRules;
389
390use strict;
391use warnings;
392use PVE::JSONSchema qw(get_standard_option);
393
394use base qw(PVE::API2::Firewall::RulesBase);
395
396__PACKAGE__->additional_parameters({
397 node => get_standard_option('pve-node'),
398 vmid => get_standard_option('pve-vmid'),
399});
400
401sub load_config {
402 my ($class, $param) = @_;
403
404 my $fw_conf = PVE::Firewall::load_vmfw_conf($param->{vmid});
405 my $rules = $fw_conf->{rules};
406
407 return ($fw_conf, $rules);
408}
409
410sub save_rules {
411 my ($class, $param, $fw_conf, $rules) = @_;
412
413 $fw_conf->{rules} = $rules;
414 PVE::Firewall::save_vmfw_conf($param->{vmid}, $fw_conf);
415}
416
417__PACKAGE__->register_handlers();
418
86791289 4191;