]> git.proxmox.com Git - pmg-api.git/blame - PMG/Cluster.pm
PMG/API2/Cluster.pm: complete cluster create, run as background task
[pmg-api.git] / PMG / Cluster.pm
CommitLineData
0854fb22
DM
1package PMG::Cluster;
2
3use strict;
4use warnings;
45e68618 5use Data::Dumper;
0854fb22 6use Socket;
cfdf6608 7use File::Path;
45e68618 8
0854fb22
DM
9use PVE::Tools;
10use PVE::INotify;
11
9f67f5b3 12use PMG::ClusterConfig;
0854fb22 13
cfdf6608
DM
14our $spooldir = "/var/spool/proxmox";
15
16sub create_needed_dirs {
17 my ($lcid, $cleanup) = @_;
18
19 # if requested, remove any stale date
20 rmtree("$spooldir/cluster") if $cleanup;
21
22 if ($lcid) {
23 mkpath "$spooldir/cluster/$lcid/virus";
24 mkpath "$spooldir/cluster/$lcid/spam";
25 }
26}
27
8737f93a
DM
28sub remote_node_ip {
29 my ($nodename, $noerr) = @_;
30
31 my $cinfo = PVE::INotify::read_file("cluster.conf");
32
45e68618 33 foreach my $entry (values %{$cinfo->{ids}}) {
8737f93a
DM
34 if ($entry->{name} eq $nodename) {
35 my $ip = $entry->{ip};
36 return $ip if !wantarray;
37 my $family = PVE::Tools::get_host_address_family($ip);
38 return ($ip, $family);
39 }
40 }
41
42 # fallback: try to get IP by other means
9f67f5b3 43 return PMG::Utils::lookup_node_ip($nodename, $noerr);
8737f93a
DM
44}
45
d2e43f9e
DM
46sub get_master_node {
47 my ($cinfo) = @_;
48
cba17aeb 49 $cinfo = PVE::INotify::read_file("cluster.conf") if !$cinfo;
d2e43f9e
DM
50
51 return $cinfo->{master}->{name} if defined($cinfo->{master});
52
53 return 'localhost';
54}
55
cba17aeb
DM
56sub read_local_ssl_cert_fingerprint {
57 my $cert_path = "/etc/pmg/pmg-api.pem";
0854fb22 58
cba17aeb
DM
59 my $cert;
60 eval {
61 my $bio = Net::SSLeay::BIO_new_file($cert_path, 'r');
62 $cert = Net::SSLeay::PEM_read_bio_X509($bio);
63 Net::SSLeay::BIO_free($bio);
64 };
65 if (my $err = $@) {
66 die "unable to read certificate '$cert_path' - $err\n";
67 }
0854fb22 68
cba17aeb
DM
69 if (!defined($cert)) {
70 die "unable to read certificate '$cert_path' - got empty value\n";
71 }
72
73 my $fp;
74 eval {
75 $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
76 };
77 if (my $err = $@) {
78 die "unable to get fingerprint for '$cert_path' - $err\n";
79 }
0854fb22 80
cba17aeb
DM
81 if (!defined($fp) || $fp eq '') {
82 die "unable to get fingerprint for '$cert_path' - got empty value\n";
83 }
0854fb22 84
cba17aeb
DM
85 return $fp;
86}
0854fb22 87
cba17aeb
DM
88my $hostrsapubkey_fn = '/etc/ssh/ssh_host_rsa_key.pub';
89my $rootrsakey_fn = '/root/.ssh/id_rsa';
90my $rootrsapubkey_fn = '/root/.ssh/id_rsa.pub';
0854fb22 91
cba17aeb
DM
92sub read_local_cluster_info {
93
94 my $res = {};
95
96 my $hostrsapubkey = PVE::Tools::file_read_firstline($hostrsapubkey_fn);
97 $hostrsapubkey =~ s/^.*ssh-rsa\s+//i;
98 $hostrsapubkey =~ s/\s+root\@\S+\s*$//i;
99
100 die "unable to parse ${hostrsapubkey_fn}\n"
101 if $hostrsapubkey !~ m/^[A-Za-z0-9\.\/\+]{200,}$/;
0854fb22
DM
102
103 my $nodename = PVE::INotify::nodename();
104
cba17aeb 105 $res->{name} = $nodename;
0854fb22 106
cba17aeb 107 $res->{ip} = PMG::Utils::lookup_node_ip($nodename);
0854fb22 108
cba17aeb 109 $res->{hostrsapubkey} = $hostrsapubkey;
0854fb22 110
cba17aeb
DM
111 if (! -f $rootrsapubkey_fn) {
112 unlink $rootrsakey_fn;
113 my $cmd = ['ssh-keygen', '-t', 'rsa', '-N', '', '-b', '2048',
114 '-f', $rootrsakey_fn];
115 PVE::Tools::run_command($cmd);
116 }
117
118 my $rootrsapubkey = PVE::Tools::file_read_firstline($rootrsapubkey_fn);
119 $rootrsapubkey =~ s/^.*ssh-rsa\s+//i;
120 $rootrsapubkey =~ s/\s+root\@\S+\s*$//i;
121
122 die "unable to parse ${rootrsapubkey_fn}\n"
123 if $rootrsapubkey !~ m/^[A-Za-z0-9\.\/\+]{200,}$/;
124
125 $res->{rootrsapubkey} = $rootrsapubkey;
126
127 $res->{fingerprint} = read_local_ssl_cert_fingerprint();
128
129 return $res;
130}
131
132# X509 Certificate cache helper
133
134my $cert_cache_nodes = {};
135my $cert_cache_timestamp = time();
136my $cert_cache_fingerprints = {};
0854fb22 137
cba17aeb 138sub update_cert_cache {
0854fb22 139
cba17aeb
DM
140 $cert_cache_timestamp = time();
141
142 $cert_cache_fingerprints = {};
143 $cert_cache_nodes = {};
144
145 my $cinfo = PVE::INotify::read_file("cluster.conf");
146
147 foreach my $entry (values %{$cinfo->{ids}}) {
148 my $node = $entry->{name};
149 my $fp = $entry->{fingerprint};
150 if ($node && $fp) {
151 $cert_cache_fingerprints->{$fp} = 1;
152 $cert_cache_nodes->{$node} = $fp;
0854fb22
DM
153 }
154 }
155}
156
157# load and cache cert fingerprint once
158sub initialize_cert_cache {
159 my ($node) = @_;
160
cba17aeb 161 update_cert_cache()
0854fb22
DM
162 if defined($node) && !defined($cert_cache_nodes->{$node});
163}
164
165sub check_cert_fingerprint {
166 my ($cert) = @_;
167
168 # clear cache every 30 minutes at least
cba17aeb 169 update_cert_cache() if time() - $cert_cache_timestamp >= 60*30;
0854fb22
DM
170
171 # get fingerprint of server certificate
172 my $fp;
173 eval {
174 $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
175 };
176 return 0 if $@ || !defined($fp) || $fp eq ''; # error
177
178 my $check = sub {
179 for my $expected (keys %$cert_cache_fingerprints) {
180 return 1 if $fp eq $expected;
181 }
182 return 0;
183 };
184
cba17aeb 185 return 1 if $check->();
0854fb22
DM
186
187 # clear cache and retry at most once every minute
188 if (time() - $cert_cache_timestamp >= 60) {
189 syslog ('info', "Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache");
190 update_cert_cache();
cba17aeb 191 return $check->();
0854fb22
DM
192 }
193
194 return 0;
195}
196
1971;