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