]> git.proxmox.com Git - pve-storage.git/blame - src/PVE/CephConfig.pm
cephconfig: escape un-escaped comment literals on write
[pve-storage.git] / src / PVE / CephConfig.pm
CommitLineData
4050fcc1 1package PVE::CephConfig;
9b7ba1db
AA
2
3use strict;
4use warnings;
5use Net::IP;
6use PVE::Tools qw(run_command);
71be0113 7use PVE::Cluster qw(cfs_register_file);
9b7ba1db 8
71be0113
DC
9cfs_register_file('ceph.conf',
10 \&parse_ceph_config,
11 \&write_ceph_config);
e34ce144 12
71be0113
DC
13sub parse_ceph_config {
14 my ($filename, $raw) = @_;
e34ce144 15
71be0113 16 my $cfg = {};
5b5534a9 17 return $cfg if !defined($raw);
e34ce144 18
71be0113 19 my @lines = split /\n/, $raw;
e34ce144
AA
20
21 my $section;
22
23 foreach my $line (@lines) {
c835d9ec 24 $line =~ s/#.*$//;
e34ce144 25 $line =~ s/^\s+//;
c835d9ec 26 $line =~ s/^;.*$//;
e34ce144
AA
27 $line =~ s/\s+$//;
28 next if !$line;
29
30 $section = $1 if $line =~ m/^\[(\S+)\]$/;
31 if (!$section) {
32 warn "no section - skip: $line\n";
33 next;
34 }
35
36 if ($line =~ m/^(.*?\S)\s*=\s*(\S.*)$/) {
9841fa31 37 my ($key, $val) = ($1, $2);
60fd7082 38 # ceph treats ' ', '_' and '-' in keys the same, so lets do too
9841fa31
DC
39 $key =~ s/[-\ ]/_/g;
40 $cfg->{$section}->{$key} = $val;
e34ce144
AA
41 }
42
43 }
44
45 return $cfg;
71be0113
DC
46}
47
48my $parse_ceph_file = sub {
49 my ($filename) = @_;
50
51 my $cfg = {};
52
53 return $cfg if ! -f $filename;
54
55 my $content = PVE::Tools::file_get_contents($filename);
56
57 return parse_ceph_config($filename, $content);
e34ce144
AA
58};
59
71be0113
DC
60sub write_ceph_config {
61 my ($filename, $cfg) = @_;
62
8777f4a6 63 my $written_sections = {};
71be0113
DC
64 my $out = '';
65
66 my $cond_write_sec = sub {
67 my $re = shift;
68
e8dbfc50 69 for my $section (sort keys $cfg->%*) {
71be0113 70 next if $section !~ m/^$re$/;
8777f4a6 71 next if exists($written_sections->{$section});
e8dbfc50 72
71be0113 73 $out .= "[$section]\n";
e8dbfc50 74 for my $key (sort keys $cfg->{$section}->%*) {
d93f8aea 75 $out .= "\t$key = $cfg->{$section}->{$key}\n";
71be0113
DC
76 }
77 $out .= "\n";
8777f4a6
MC
78
79 $written_sections->{$section} = 1;
71be0113
DC
80 }
81 };
82
e8dbfc50
MC
83 my @rexprs = (
84 qr/global/,
f9d9bd32 85
e8dbfc50 86 qr/client/,
f9d9bd32 87 qr/client\..*/,
e8dbfc50
MC
88
89 qr/mds/,
e8dbfc50 90 qr/mds\..*/,
f9d9bd32
MC
91
92 qr/mon/,
e8dbfc50 93 qr/mon\..*/,
f9d9bd32
MC
94
95 qr/osd/,
e8dbfc50 96 qr/osd\..*/,
f9d9bd32
MC
97
98 qr/mgr/,
e8dbfc50 99 qr/mgr\..*/,
8777f4a6
MC
100
101 qr/.*/,
e8dbfc50 102 );
71be0113 103
e8dbfc50
MC
104 for my $re (@rexprs) {
105 $cond_write_sec->($re);
106 }
71be0113 107
ad6bcc9e
MC
108 # Escape comment literals that aren't escaped already
109 $out =~ s/(?<!\\)([;#])/\\$1/gm;
110
71be0113
DC
111 return $out;
112}
113
e34ce144
AA
114my $ceph_get_key = sub {
115 my ($keyfile, $username) = @_;
116
117 my $key = $parse_ceph_file->($keyfile);
118 my $secret = $key->{"client.$username"}->{key};
119
120 return $secret;
121};
122
c8a32345
DC
123my $get_host = sub {
124 my ($hostport) = @_;
125 my ($host, $port) = PVE::Tools::parse_host_and_port($hostport);
126 if (!defined($host)) {
127 return "";
128 }
129 $port = defined($port) ? ":$port" : '';
130 $host = "[$host]" if Net::IP::ip_is_ipv6($host);
131 return "${host}${port}";
132};
133
e34ce144
AA
134sub get_monaddr_list {
135 my ($configfile) = shift;
136
e34ce144
AA
137 if (!defined($configfile)) {
138 warn "No ceph config specified\n";
139 return;
140 }
141
142 my $config = $parse_ceph_file->($configfile);
e34ce144 143
5b79dac9 144 my $monhostlist = {};
4b3088a0 145
ffc31266 146 # get all ip addresses from mon_host
5b79dac9
DC
147 my $monhosts = [ split (/[ ,;]+/, $config->{global}->{mon_host} // "") ];
148
149 foreach my $monhost (@$monhosts) {
150 $monhost =~ s/^\[?v\d\://; # remove beginning of vector
151 $monhost =~ s|/\d+\]?||; # remove end of vector
152 my $host = $get_host->($monhost);
153 if ($host ne "") {
154 $monhostlist->{$host} = 1;
155 }
156 }
157
158 # then get all addrs from mon. sections
159 for my $section ( keys %$config ) {
160 next if $section !~ m/^mon\./;
161
162 if (my $addr = $config->{$section}->{mon_addr}) {
163 $monhostlist->{$addr} = 1;
164 }
165 }
166
167 return join(',', sort keys %$monhostlist);
168}
e34ce144 169
9b7ba1db
AA
170sub hostlist {
171 my ($list_text, $separator) = @_;
172
173 my @monhostlist = PVE::Tools::split_list($list_text);
c8a32345 174 return join($separator, map { $get_host->($_) } @monhostlist);
9b7ba1db
AA
175}
176
5527c824
TL
177my $ceph_check_keyfile = sub {
178 my ($filename, $type) = @_;
179
180 return if ! -f $filename;
181
182 my $content = PVE::Tools::file_get_contents($filename);
183 eval {
184 die if !$content;
185
186 if ($type eq 'rbd') {
187 die if $content !~ /\s*\[\S+\]\s*key\s*=\s*\S+==\s*$/m;
188 } elsif ($type eq 'cephfs') {
189 die if $content !~ /\S+==\s*$/;
190 }
191 };
192 die "Not a proper $type authentication file: $filename\n" if $@;
193
194 return undef;
195};
196
9b7ba1db
AA
197sub ceph_connect_option {
198 my ($scfg, $storeid, %options) = @_;
199
200 my $cmd_option = {};
9b7ba1db
AA
201 my $keyfile = "/etc/pve/priv/ceph/${storeid}.keyring";
202 $keyfile = "/etc/pve/priv/ceph/${storeid}.secret" if ($scfg->{type} eq 'cephfs');
203 my $pveceph_managed = !defined($scfg->{monhost});
204
428872eb 205 $cmd_option->{ceph_conf} = '/etc/pve/ceph.conf' if $pveceph_managed;
9b7ba1db 206
5527c824 207 $ceph_check_keyfile->($keyfile, $scfg->{type});
3e479172 208
428872eb
TL
209 if (-e "/etc/pve/priv/ceph/${storeid}.conf") {
210 # allow custom ceph configuration for external clusters
9b7ba1db
AA
211 if ($pveceph_managed) {
212 warn "ignoring custom ceph config for storage '$storeid', 'monhost' is not set (assuming pveceph managed cluster)!\n";
213 } else {
428872eb 214 $cmd_option->{ceph_conf} = "/etc/pve/priv/ceph/${storeid}.conf";
9b7ba1db
AA
215 }
216 }
217
218 $cmd_option->{keyring} = $keyfile if (-e $keyfile);
219 $cmd_option->{auth_supported} = (defined $cmd_option->{keyring}) ? 'cephx' : 'none';
220 $cmd_option->{userid} = $scfg->{username} ? $scfg->{username} : 'admin';
221 $cmd_option->{mon_host} = hostlist($scfg->{monhost}, ',') if (defined($scfg->{monhost}));
222
223 if (%options) {
224 foreach my $k (keys %options) {
225 $cmd_option->{$k} = $options{$k};
226 }
227 }
228
229 return $cmd_option;
230
231}
232
e34ce144 233sub ceph_create_keyfile {
a4a1fe64 234 my ($type, $storeid, $secret) = @_;
e34ce144
AA
235
236 my $extension = 'keyring';
237 $extension = 'secret' if ($type eq 'cephfs');
238
239 my $ceph_admin_keyring = '/etc/pve/priv/ceph.client.admin.keyring';
240 my $ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension";
241
242 die "ceph authx keyring file for storage '$storeid' already exists!\n"
a4a1fe64 243 if -e $ceph_storage_keyring && !defined($secret);
e34ce144 244
a4a1fe64 245 if (-e $ceph_admin_keyring || defined($secret)) {
e34ce144 246 eval {
a4a1fe64
AL
247 if (defined($secret)) {
248 mkdir '/etc/pve/priv/ceph';
e7bc1f03
AL
249 chomp $secret;
250 PVE::Tools::file_set_contents($ceph_storage_keyring, "${secret}\n", 0400);
a4a1fe64 251 } elsif ($type eq 'rbd') {
e34ce144
AA
252 mkdir '/etc/pve/priv/ceph';
253 PVE::Tools::file_copy($ceph_admin_keyring, $ceph_storage_keyring);
254 } elsif ($type eq 'cephfs') {
a4a1fe64 255 my $cephfs_secret = $ceph_get_key->($ceph_admin_keyring, 'admin');
e34ce144 256 mkdir '/etc/pve/priv/ceph';
e7bc1f03
AL
257 chomp $cephfs_secret;
258 PVE::Tools::file_set_contents($ceph_storage_keyring, "${cephfs_secret}\n", 0400);
e34ce144
AA
259 }
260 };
261 if (my $err = $@) {
262 unlink $ceph_storage_keyring;
263 die "failed to copy ceph authx $extension for storage '$storeid': $err\n";
264 }
265 } else {
266 warn "$ceph_admin_keyring not found, authentication is disabled.\n";
267 }
268}
269
270sub ceph_remove_keyfile {
271 my ($type, $storeid) = @_;
272
273 my $extension = 'keyring';
274 $extension = 'secret' if ($type eq 'cephfs');
275 my $ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension";
276
277 if (-f $ceph_storage_keyring) {
278 unlink($ceph_storage_keyring) or warn "removing keyring of storage failed: $!\n";
279 }
280}
281
e54c3e33
AA
282my $ceph_version_parser = sub {
283 my $ceph_version = shift;
284 # FIXME this is the same as pve-manager PVE::Ceph::Tools get_local_version
5c04a0b3 285 if ($ceph_version =~ /^ceph.*\sv?(\d+(?:\.\d+)+(?:-pve\d+)?)\s+(?:\(([a-zA-Z0-9]+)\))?/) {
e54c3e33
AA
286 my ($version, $buildcommit) = ($1, $2);
287 my $subversions = [ split(/\.|-/, $version) ];
288
289 return ($subversions, $version, $buildcommit);
290 }
291 warn "Could not parse Ceph version: '$ceph_version'\n";
292};
293
7435dc90 294sub local_ceph_version {
e54c3e33
AA
295 my ($cache) = @_;
296
297 my $version_string = $cache;
298 if (!defined($version_string)) {
299 run_command('ceph --version', outfunc => sub {
300 $version_string = shift;
301 });
302 }
303 return undef if !defined($version_string);
304 # subversion is an array ref. with the version parts from major to minor
305 # version is the filtered version string
306 my ($subversions, $version) = $ceph_version_parser->($version_string);
307
308 return wantarray ? ($subversions, $version) : $version;
309}
310
9b7ba1db 3111;