]>
git.proxmox.com Git - pmg-api.git/blob - PMG/Cluster.pm
10 # this is also used to get the IP of the local node
12 my ($nodename, $noerr) = @_;
14 # todo: implement cluster node list
16 # fallback: try to get IP by other means
17 my ($family, $packed_ip);
20 my @res = PVE
::Tools
::getaddrinfo_all
($nodename);
21 $family = $res[0]->{family
};
22 $packed_ip = (PVE
::Tools
::unpack_sockaddr_in46
($res[0]->{addr
}))[2];
26 die "hostname lookup failed:\n$@" if !$noerr;
30 my $ip = Socket
::inet_ntop
($family, $packed_ip);
31 if ($ip =~ m/^127\.|^::1$/) {
32 die "hostname lookup failed - got local IP address ($nodename = $ip)\n" if !$noerr;
36 return wantarray ?
($ip, $family) : $ip;
39 # X509 Certificate cache helper
41 my $cert_cache_nodes = {};
42 my $cert_cache_timestamp = time();
43 my $cert_cache_fingerprints = {};
45 sub update_cert_cache
{
46 my ($update_node, $clear) = @_;
48 syslog
('info', "Clearing outdated entries from certificate cache")
51 $cert_cache_timestamp = time() if !defined($update_node);
53 my $node_list = defined($update_node) ?
54 [ $update_node ] : [ keys %$cert_cache_nodes ];
56 my $clear_node = sub {
58 if (my $old_fp = $cert_cache_nodes->{$node}) {
59 # distrust old fingerprint
60 delete $cert_cache_fingerprints->{$old_fp};
61 # ensure reload on next proxied request
62 delete $cert_cache_nodes->{$node};
66 my $nodename = PVE
::INotify
::nodename
();
68 foreach my $node (@$node_list) {
70 if ($node ne $nodename) {
71 &$clear_node($node) if $clear;
75 my $cert_path = "/etc/proxmox/pmg-api.pem";
79 my $bio = Net
::SSLeay
::BIO_new_file
($cert_path, 'r');
80 $cert = Net
::SSLeay
::PEM_read_bio_X509
($bio);
81 Net
::SSLeay
::BIO_free
($bio);
84 if ($err || !defined($cert)) {
85 &$clear_node($node) if $clear;
91 $fp = Net
::SSLeay
::X509_get_fingerprint
($cert, 'sha256');
94 if ($err || !defined($fp) || $fp eq '') {
95 &$clear_node($node) if $clear;
99 my $old_fp = $cert_cache_nodes->{$node};
100 $cert_cache_fingerprints->{$fp} = 1;
101 $cert_cache_nodes->{$node} = $fp;
103 if (defined($old_fp) && $fp ne $old_fp) {
104 delete $cert_cache_fingerprints->{$old_fp};
109 # load and cache cert fingerprint once
110 sub initialize_cert_cache
{
113 update_cert_cache
($node)
114 if defined($node) && !defined($cert_cache_nodes->{$node});
117 sub check_cert_fingerprint
{
120 # clear cache every 30 minutes at least
121 update_cert_cache
(undef, 1) if time() - $cert_cache_timestamp >= 60*30;
123 # get fingerprint of server certificate
126 $fp = Net
::SSLeay
::X509_get_fingerprint
($cert, 'sha256');
128 return 0 if $@ || !defined($fp) || $fp eq ''; # error
131 for my $expected (keys %$cert_cache_fingerprints) {
132 return 1 if $fp eq $expected;
137 return 1 if &$check();
139 # clear cache and retry at most once every minute
140 if (time() - $cert_cache_timestamp >= 60) {
141 syslog
('info', "Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache");
149 sub read_cluster_conf
{
150 my ($filename, $fh) = @_;
152 my $localname = PVE
::INotify
::nodename
();
153 my $localip = remote_node_ip
($localname);
160 $cinfo->{nodes
} = [];
161 $cinfo->{remnodes
} = [];
174 $cinfo->{exists} = 1; # cluster configuratin file exists and is readable
176 while (defined(my $line = <$fh>)) {
179 next if $line =~ m/^\s*$/; # skip empty lines
181 if ($line =~ m/^maxcid\s+(\d+)\s*$/i) {
182 $maxcid = $1 > $maxcid ?
$1 : $maxcid;
186 if ($line =~ m/^(master|node)\s+(\d+)\s+\{\s*$/i) {
188 my ($t, $cid) = (lc($1), $2);
190 $maxcid = $cid > $maxcid ?
$cid : $maxcid;
193 role => $t eq 'master' ?
'M' : 'N',
197 while (defined($line = <$fh>)) {
199 next if $line =~ m/^\s*$/; # skip empty lines
200 if ($line =~ m/^\}\s*$/) {
205 if ($line =~ m/^\s*(\S+)\s*:\s*(\S+)\s*$/) {
206 my ($n, $v) = (lc $1, $2);
208 # fixme: do syntax checks
211 } elsif ($n eq 'name') {
213 } elsif ($n eq 'hostrsapubkey') {
215 } elsif ($n eq 'rootrsapubkey') {
218 die "syntax error in configuration file\n";
221 die "syntax error in configuration file\n";
225 die "missing ip address for node '$cid'\n" if !$res->{ip
};
226 die "missing name for node '$cid'\n" if !$res->{name
};
227 #die "missing host RSA key for node '$cid'\n" if !$res->{hostrsapubkey};
228 #die "missing user RSA key for node '$cid'\n" if !$res->{rootrsapubkey};
230 push @{$cinfo->{nodes
}}, $res;
232 if ($res->{role} eq 'M') {
233 $cinfo->{master
} = $res;
236 if ($res->{ip
} eq $localip) {
237 $cinfo->{local} = $res;
240 die "syntax error in configuration file\n";
245 die "syntax error in configuration file\n" if $level;
247 $cinfo->{maxcid
} = $maxcid;
250 foreach my $ni (@{$cinfo->{nodes
}}) {
251 next if $cinfo->{local}->{cid
} == $ni->{cid
}; # skip local CID
252 push @cidlist, $ni->{cid
};
257 foreach my $cid (sort @cidlist) {
258 $portid->{$cid} = $ind;
262 foreach my $ni (@{$cinfo->{nodes
}}) {
263 # fixme: do we still need those ports?
264 $ni->{configport
} = $ni->{cid
} == $cinfo->{local}->{cid
} ?
83 : 50000 + $portid->{$ni->{cid
}};
265 $ni->{dbport
} = $ni->{cid
} == $cinfo->{local}->{cid
} ?
5432 : 50100 + $portid->{$ni->{cid
}};
268 foreach my $ni (@{$cinfo->{nodes
}}) {
269 next if $ni->{cid
} == $cinfo->{local}->{cid
};
270 push @{$cinfo->{remnodes
}}, $ni->{cid
};
276 sub write_cluster_conf
{
277 my ($filename, $fh, $cinfo) = @_;
279 my $raw = "maxcid $cinfo->{maxcid}\n\n";
281 foreach my $ni (@{$cinfo->{nodes
}}) {
283 if ($ni->{role} eq 'M') {
284 $raw .= "master $ni->{cid} {\n";
285 $raw .= " IP: $ni->{ip}\n";
286 $raw .= " NAME: $ni->{name}\n";
287 $raw .= " HOSTRSAPUBKEY: $ni->{hostrsapubkey}\n";
288 $raw .= " ROOTRSAPUBKEY: $ni->{rootrsapubkey}\n";
290 } elsif ($ni->{role} eq 'N') {
291 $raw .= "node $ni->{cid} {\n";
292 $raw .= " IP: $ni->{ip}\n";
293 $raw .= " NAME: $ni->{name}\n";
294 $raw .= " HOSTRSAPUBKEY: $ni->{hostrsapubkey}\n";
295 $raw .= " ROOTRSAPUBKEY: $ni->{rootrsapubkey}\n";
300 PVE
::Tools
::safe_print
($filename, $fh, $raw);
303 PVE
::INotify
::register_file
('cluster.conf', "/etc/proxmox/cluster.conf",
305 \
&write_cluster_conf
,
307 always_call_parser
=> 1);