]> git.proxmox.com Git - pve-cluster.git/blame - data/PVE/DataCenterConfig.pm
datacenter config: add options to control tag usage rights
[pve-cluster.git] / data / PVE / DataCenterConfig.pm
CommitLineData
ab966729
FG
1package PVE::DataCenterConfig;
2
3use strict;
4use warnings;
5
51866274 6use PVE::JSONSchema qw(parse_property_string);
ab966729
FG
7use PVE::Tools;
8use PVE::Cluster;
9
10my $migration_format = {
11 type => {
12 default_key => 1,
13 type => 'string',
14 enum => ['secure', 'insecure'],
15 description => "Migration traffic is encrypted using an SSH tunnel by " .
16 "default. On secure, completely private networks this can be " .
17 "disabled to increase performance.",
18 default => 'secure',
19 },
20 network => {
21 optional => 1,
22 type => 'string', format => 'CIDR',
23 format_description => 'CIDR',
24 description => "CIDR of the (sub) network that is used for migration."
25 },
26};
27
28my $ha_format = {
29 shutdown_policy => {
30 type => 'string',
28c22b8d
TL
31 enum => ['freeze', 'failover', 'conditional', 'migrate'],
32 description => "The policy for HA services on node shutdown. 'freeze' disables ".
33 "auto-recovery, 'failover' ensures recovery, 'conditional' recovers on ".
34 "poweroff and freezes on reboot. 'migrate' will migrate running services ".
35 "to other nodes, if possible. With 'freeze' or 'failover', HA Services will ".
36 "always get stopped first on shutdown.",
37 verbose_description => "Describes the policy for handling HA services on poweroff ".
38 "or reboot of a node. Freeze will always freeze services which are still located ".
39 "on the node on shutdown, those services won't be recovered by the HA manager. ".
40 "Failover will not mark the services as frozen and thus the services will get ".
41 "recovered to other nodes, if the shutdown node does not come up again quickly ".
42 "(< 1min). 'conditional' chooses automatically depending on the type of shutdown, ".
43 "i.e., on a reboot the service will be frozen but on a poweroff the service will ".
1818509b 44 "stay as is, and thus get recovered after about 2 minutes. ".
dc43b8a4
TL
45 "Migrate will try to move all running services to another node when a reboot or ".
46 "shutdown was triggered. The poweroff process will only continue once no running services ".
1818509b
TL
47 "are located on the node anymore. If the node comes up again, the service will ".
48 "be moved back to the previously powered-off node, at least if no other migration, ".
49 "reloaction or recovery took place.",
ab966729
FG
50 default => 'conditional',
51 }
52};
53
75e5d02e
TL
54my $next_id_format = {
55 lower => {
56 type => 'integer',
57 description => "Lower, inclusive boundary for free next-id API range.",
58 min => 100,
59 max => 1000 * 1000 * 1000 - 1,
60 default => 100,
61 optional => 1,
62 },
63 upper => {
64 type => 'integer',
8ca3c4bb 65 description => "Upper, exclusive boundary for free next-id API range.",
75e5d02e 66 min => 100,
8ca3c4bb 67 max => 1000 * 1000 * 1000,
75e5d02e
TL
68 default => 1000 * 1000, # lower than the maximum on purpose
69 optional => 1,
70 },
71};
72
bcfa5ac1 73my $u2f_format = {
ab966729
FG
74 appid => {
75 type => 'string',
76 description => "U2F AppId URL override. Defaults to the origin.",
77 format_description => 'APPID',
78 optional => 1,
79 },
80 origin => {
81 type => 'string',
82 description => "U2F Origin override. Mostly useful for single nodes with a single URL.",
83 format_description => 'URL',
84 optional => 1,
85 },
86};
87
8545a705
WB
88my $webauthn_format = {
89 rp => {
90 type => 'string',
91 description =>
92 'Relying party name. Any text identifier.'
93 .' Changing this *may* break existing credentials.',
94 format_description => 'RELYING_PARTY',
95 optional => 1,
96 },
97 origin => {
98 type => 'string',
99 description =>
100 'Site origin. Must be a `https://` URL (or `http://localhost`).'
101 .' Should contain the address users type in their browsers to access'
102 .' the web interface.'
103 .' Changing this *may* break existing credentials.',
104 format_description => 'URL',
105 optional => 1,
106 },
107 id => {
108 type => 'string',
109 description =>
3b0214b6 110 'Relying party ID. Must be the domain name without protocol, port or location.'
8545a705
WB
111 .' Changing this *will* break existing credentials.',
112 format_description => 'DOMAINNAME',
113 optional => 1,
114 },
3b0214b6
WB
115 'allow-subdomains' => {
116 type => 'boolean',
117 description => 'Whether to allow the origin to be a subdomain, rather than the exact URL.',
118 optional => 1,
119 default => 1,
120 },
8545a705 121};
ab966729
FG
122
123PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
124sub pve_verify_mac_prefix {
125 my ($mac_prefix, $noerr) = @_;
126
127 if ($mac_prefix !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){0,2}:?$/i) {
128 return undef if $noerr;
129 die "value is not a valid unicast MAC address prefix\n";
130 }
131 return $mac_prefix;
132}
133
af234c4b
DC
134my $COLOR_RE = '[0-9a-fA-F]{6}';
135my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
136
137my $tag_style_format = {
138 'shape' => {
139 optional => 1,
140 type => 'string',
141 enum => ['full', 'circle', 'dense', 'none'],
142 default => 'circle',
143 description => "Tag shape for the web ui tree. 'full' draws the full tag. "
144 ."'circle' draws only a circle with the background color. "
145 ."'dense' only draws a small rectancle (useful when many tags are assigned to each guest)."
146 ."'none' disables showing the tags.",
147 },
148 'color-map' => {
149 optional => 1,
150 type => 'string',
151 pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
152 typetext => '<tag>:<hex-color>[:<hex-color-for-text>][;<tag>=...]',
153 description => "Manual color mapping for tags (semicolon separated).",
154 },
155};
156
c17e397b
DC
157my $user_tag_privs_format = {
158 'user-allow' => {
159 optional => 1,
160 type => 'string',
161 enum => ['none', 'list', 'existing', 'free'],
162 default => 'free',
163 description => "Controls tag usage for users without `Sys.Modify` on `/` by either "
164 ."allowing `none`, a `list`, already `existing` or anything (`free`).",
165 verbose_description => "Controls which tags can be set or deleted on resources a user "
166 ."controls (such as guests). Users with the `Sys.Modify` privilege on `/` are always "
167 ." unrestricted. "
168 ."'none' no tags are usable. "
169 ."'list' tasg from 'user-allow'list' are usable. "
170 ."'existing' like list, but already existing tags of resources are also usable."
171 ."'free' no tag restrictions."
172 },
173 'user-allow-list' => {
174 optional => 1,
175 type => 'string',
176 pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*",
177 typetext => "<tag>[;<tag>...]",
178 description => "List of tags users are allowed to set and delete (semicolon separated) "
179 ."for 'user-allow' values 'list' and 'existing'.",
180 },
181};
182
ab966729
FG
183my $datacenter_schema = {
184 type => "object",
185 additionalProperties => 0,
186 properties => {
187 keyboard => {
188 optional => 1,
189 type => 'string',
190 description => "Default keybord layout for vnc server.",
191 enum => PVE::Tools::kvmkeymaplist(),
192 },
193 language => {
194 optional => 1,
195 type => 'string',
196 description => "Default GUI language.",
197 enum => [
198 'ca',
199 'da',
200 'de',
201 'en',
202 'es',
203 'eu',
204 'fa',
205 'fr',
206 'he',
207 'it',
208 'ja',
209 'nb',
210 'nn',
211 'pl',
212 'pt_BR',
213 'ru',
214 'sl',
215 'sv',
216 'tr',
217 'zh_CN',
218 'zh_TW',
219 ],
220 },
221 http_proxy => {
222 optional => 1,
223 type => 'string',
224 description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
225 pattern => "http://.*",
226 },
0cc6e737 227 # FIXME: remove with 8.0 (add check to pve7to8!), merged into "migration" since 4.3
ab966729
FG
228 migration_unsecure => {
229 optional => 1,
230 type => 'boolean',
231 description => "Migration is secure using SSH tunnel by default. " .
232 "For secure private networks you can disable it to speed up " .
233 "migration. Deprecated, use the 'migration' property instead!",
234 },
75e5d02e
TL
235 'next-id' => {
236 optional => 1,
237 type => 'string',
238 format => $next_id_format,
239 description => "Control the range for the free VMID auto-selection pool.",
240 },
ab966729
FG
241 migration => {
242 optional => 1,
243 type => 'string', format => $migration_format,
244 description => "For cluster wide migration settings.",
245 },
246 console => {
247 optional => 1,
248 type => 'string',
0cc6e737
TL
249 description => "Select the default Console viewer. You can either use the builtin java"
250 ." applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
251 # FIXME: remove 'applet' with 8.0 (add pve7to8 check!)
ab966729
FG
252 enum => ['applet', 'vv', 'html5', 'xtermjs'],
253 },
254 email_from => {
255 optional => 1,
256 type => 'string',
257 format => 'email-opt',
258 description => "Specify email address to send notification from (default is root@\$hostname)",
259 },
260 max_workers => {
261 optional => 1,
262 type => 'integer',
263 minimum => 1,
264 description => "Defines how many workers (per node) are maximal started ".
265 " on actions like 'stopall VMs' or task from the ha-manager.",
266 },
267 fencing => {
268 optional => 1,
269 type => 'string',
270 default => 'watchdog',
271 enum => [ 'watchdog', 'hardware', 'both' ],
272 description => "Set the fencing mode of the HA cluster. Hardware mode " .
273 "needs a valid configuration of fence devices in /etc/pve/ha/fence.cfg." .
274 " With both all two modes are used." .
275 "\n\nWARNING: 'hardware' and 'both' are EXPERIMENTAL & WIP",
276 },
277 ha => {
278 optional => 1,
279 type => 'string', format => $ha_format,
280 description => "Cluster wide HA settings.",
281 },
282 mac_prefix => {
283 optional => 1,
284 type => 'string',
285 format => 'mac-prefix',
286 description => 'Prefix for autogenerated MAC addresses.',
287 },
288 bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
289 u2f => {
290 optional => 1,
291 type => 'string',
292 format => $u2f_format,
293 description => 'u2f',
294 },
8545a705
WB
295 webauthn => {
296 optional => 1,
297 type => 'string',
298 format => $webauthn_format,
299 description => 'webauthn configuration',
300 },
2ae1c0bb
DJ
301 description => {
302 type => 'string',
303 description => "Datacenter description. Shown in the web-interface datacenter notes panel."
304 ." This is saved as comment inside the configuration file.",
305 maxLength => 64 * 1024,
306 optional => 1,
307 },
af234c4b
DC
308 'tag-style' => {
309 optional => 1,
310 type => 'string',
311 description => "Tag style options.",
312 format => $tag_style_format,
313 },
c17e397b
DC
314 'user-tag-access' => {
315 optional => 1,
316 type => 'string',
317 description => "Privilege options for user-settable tags",
318 format => $user_tag_privs_format,
319 },
320 'registered-tags' => {
321 optional => 1,
322 type => 'string',
323 description => "A list of tags that require a `Sys.Modify` on '/' to set and delete. "
324 ."Tags set here that are also in 'user-tag-access' also require `Sys.Modify`.",
325 pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}",
326 typetext => "<tag>[;<tag>...]",
327 },
ab966729
FG
328 },
329};
330
331# make schema accessible from outside (for documentation)
332sub get_datacenter_schema { return $datacenter_schema };
333
334sub parse_datacenter_config {
335 my ($filename, $raw) = @_;
336
b5e2b244
TL
337 $raw = '' if !defined($raw);
338
2ae1c0bb
DJ
339 # description may be comment or key-value pair (or both)
340 my $comment = '';
459f6084 341 for my $line (split(/\n/, $raw)) {
09b99550 342 if ($line =~ /^\#(.*)$/) {
2ae1c0bb
DJ
343 $comment .= PVE::Tools::decode_text($1) . "\n";
344 }
345 }
346
347 # parse_config ignores lines with # => use $raw
b5e2b244 348 my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw);
ab966729 349
2ae1c0bb
DJ
350 $res->{description} = $comment;
351
ab966729 352 if (my $migration = $res->{migration}) {
51866274 353 $res->{migration} = parse_property_string($migration_format, $migration);
ab966729
FG
354 }
355
75e5d02e
TL
356 if (my $next_id = $res->{'next-id'}) {
357 $res->{'next-id'} = parse_property_string($next_id_format, $next_id);
358 }
359
ab966729 360 if (my $ha = $res->{ha}) {
51866274 361 $res->{ha} = parse_property_string($ha_format, $ha);
ab966729
FG
362 }
363
bcfa5ac1 364 if (my $u2f = $res->{u2f}) {
51866274 365 $res->{u2f} = parse_property_string($u2f_format, $u2f);
bcfa5ac1
FG
366 }
367
8545a705 368 if (my $webauthn = $res->{webauthn}) {
51866274 369 $res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
8545a705
WB
370 }
371
af234c4b
DC
372 if (my $tag_style = $res->{'tag-style'}) {
373 $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style);
374 }
375
c17e397b
DC
376 if (my $user_tag_privs = $res->{'user-tag-access'}) {
377 $res->{'user-tag-access'} =
378 parse_property_string($user_tag_privs_format, $user_tag_privs);
379
380 if (my $user_tags = $res->{'user-tag-access'}->{'user-allow-list'}) {
381 $res->{'user-tag-access'}->{'user-allow-list'} = [split(';', $user_tags)];
382 }
383 }
384
385 if (my $admin_tags = $res->{'registered-tags'}) {
386 $res->{'registered-tags'} = [split(';', $admin_tags)];
387 }
388
ab966729
FG
389 # for backwards compatibility only, new migration property has precedence
390 if (defined($res->{migration_unsecure})) {
391 if (defined($res->{migration}->{type})) {
392 warn "deprecated setting 'migration_unsecure' and new 'migration: type' " .
393 "set at same time! Ignore 'migration_unsecure'\n";
394 } else {
395 $res->{migration}->{type} = ($res->{migration_unsecure}) ? 'insecure' : 'secure';
396 }
397 }
398
399 # for backwards compatibility only, applet maps to html5
400 if (defined($res->{console}) && $res->{console} eq 'applet') {
401 $res->{console} = 'html5';
402 }
403
404 return $res;
405}
406
407sub write_datacenter_config {
408 my ($filename, $cfg) = @_;
409
410 # map deprecated setting to new one
411 if (defined($cfg->{migration_unsecure}) && !defined($cfg->{migration})) {
412 my $migration_unsecure = delete $cfg->{migration_unsecure};
413 $cfg->{migration}->{type} = ($migration_unsecure) ? 'insecure' : 'secure';
414 }
415
416 # map deprecated applet setting to html5
417 if (defined($cfg->{console}) && $cfg->{console} eq 'applet') {
418 $cfg->{console} = 'html5';
419 }
420
5b576e26 421 if (ref(my $migration = $cfg->{migration})) {
ab966729
FG
422 $cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
423 }
424
75e5d02e
TL
425 if (defined(my $next_id = $cfg->{'next-id'})) {
426 $next_id = parse_property_string($next_id_format, $next_id) if !ref($next_id);
427
428 my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
429 my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
430
fe2bc758 431 die "lower ($lower) <= upper ($upper) boundary rule broken\n" if $lower > $upper;
75e5d02e
TL
432
433 $cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
434 }
435
5b576e26 436 if (ref(my $ha = $cfg->{ha})) {
ab966729
FG
437 $cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
438 }
439
5b576e26 440 if (ref(my $u2f = $cfg->{u2f})) {
bcfa5ac1
FG
441 $cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
442 }
443
5b576e26 444 if (ref(my $webauthn = $cfg->{webauthn})) {
8545a705
WB
445 $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
446 }
447
af234c4b
DC
448 if (ref(my $tag_style = $cfg->{'tag-style'})) {
449 $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
450 }
451
c17e397b
DC
452 if (ref(my $user_tag_privs = $cfg->{'user-tag-access'})) {
453 if (my $user_tags = $user_tag_privs->{'user-allow-list'}) {
454 $user_tag_privs->{'user-allow-list'} = join(';', sort $user_tags->@*);
455 }
456 $cfg->{'user-tag-access'} =
457 PVE::JSONSchema::print_property_string($user_tag_privs, $user_tag_privs_format);
458 }
459
460 if (ref(my $admin_tags = $cfg->{'registered-tags'})) {
461 $cfg->{'registered-tags'} = join(';', sort $admin_tags->@*);
462 }
463
2ae1c0bb
DJ
464 my $comment = '';
465 # add description as comment to top of file
466 my $description = $cfg->{description} || '';
467 foreach my $line (split(/\n/, $description)) {
468 $comment .= '#' . PVE::Tools::encode_text($line) . "\n";
469 }
470 delete $cfg->{description}; # add only as comment, no additional key-value pair
471 my $dump = PVE::JSONSchema::dump_config($datacenter_schema, $filename, $cfg);
472
473 return $comment . "\n" . $dump;
ab966729
FG
474}
475
02ce710f
TL
476PVE::Cluster::cfs_register_file(
477 'datacenter.cfg',
478 \&parse_datacenter_config,
479 \&write_datacenter_config,
480);
ab966729
FG
481
4821;