]>
Commit | Line | Data |
---|---|---|
0854fb22 DM |
1 | package PMG::Cluster; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
45e68618 | 5 | use Data::Dumper; |
0854fb22 | 6 | use Socket; |
cfdf6608 | 7 | use File::Path; |
45e68618 | 8 | |
0854fb22 DM |
9 | use PVE::Tools; |
10 | use PVE::INotify; | |
11 | ||
9f67f5b3 | 12 | use PMG::ClusterConfig; |
0854fb22 | 13 | |
cfdf6608 DM |
14 | our $spooldir = "/var/spool/proxmox"; |
15 | ||
16 | sub 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 |
28 | sub 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 |
46 | sub 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 |
56 | sub 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 |
88 | my $hostrsapubkey_fn = '/etc/ssh/ssh_host_rsa_key.pub'; |
89 | my $rootrsakey_fn = '/root/.ssh/id_rsa'; | |
90 | my $rootrsapubkey_fn = '/root/.ssh/id_rsa.pub'; | |
0854fb22 | 91 | |
cba17aeb DM |
92 | sub 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 | ||
134 | my $cert_cache_nodes = {}; | |
135 | my $cert_cache_timestamp = time(); | |
136 | my $cert_cache_fingerprints = {}; | |
0854fb22 | 137 | |
cba17aeb | 138 | sub 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 | |
158 | sub 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 | ||
165 | sub 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 | ||
197 | 1; |