Revision 0602cef3
b/lib/tools/prepare_node_join.py | ||
---|---|---|
27 | 27 |
import optparse |
28 | 28 |
import sys |
29 | 29 |
import logging |
30 |
import errno |
|
31 | 30 |
import OpenSSL |
32 | 31 |
|
33 | 32 |
from ganeti import cli |
... | ... | |
94 | 93 |
return opts |
95 | 94 |
|
96 | 95 |
|
97 |
def _VerifyCertificate(cert, _noded_cert_file=pathutils.NODED_CERT_FILE):
|
|
96 |
def _VerifyCertificate(cert_pem, _check_fn=utils.CheckNodeCertificate):
|
|
98 | 97 |
"""Verifies a certificate against the local node daemon certificate. |
99 | 98 |
|
100 |
@type cert: string |
|
101 |
@param cert: Certificate in PEM format (no key) |
|
99 |
@type cert_pem: string
|
|
100 |
@param cert_pem: Certificate in PEM format (no key)
|
|
102 | 101 |
|
103 | 102 |
""" |
104 | 103 |
try: |
105 |
OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert) |
|
104 |
OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
|
|
106 | 105 |
except OpenSSL.crypto.Error, err: |
107 | 106 |
pass |
108 | 107 |
else: |
109 | 108 |
raise JoinError("No private key may be given") |
110 | 109 |
|
111 | 110 |
try: |
112 |
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) |
|
111 |
cert = \ |
|
112 |
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem) |
|
113 | 113 |
except Exception, err: |
114 | 114 |
raise errors.X509CertError("(stdin)", |
115 | 115 |
"Unable to load certificate: %s" % err) |
116 | 116 |
|
117 |
try: |
|
118 |
noded_pem = utils.ReadFile(_noded_cert_file) |
|
119 |
except EnvironmentError, err: |
|
120 |
if err.errno != errno.ENOENT: |
|
121 |
raise |
|
122 |
|
|
123 |
logging.debug("Local node certificate was not found (file %s)", |
|
124 |
_noded_cert_file) |
|
125 |
return |
|
126 |
|
|
127 |
try: |
|
128 |
key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, noded_pem) |
|
129 |
except Exception, err: |
|
130 |
raise errors.X509CertError(_noded_cert_file, |
|
131 |
"Unable to load private key: %s" % err) |
|
132 |
|
|
133 |
check_fn = utils.PrepareX509CertKeyCheck(cert, key) |
|
134 |
try: |
|
135 |
check_fn() |
|
136 |
except OpenSSL.SSL.Error: |
|
137 |
raise JoinError("Given cluster certificate does not match local key") |
|
117 |
_check_fn(cert) |
|
138 | 118 |
|
139 | 119 |
|
140 | 120 |
def VerifyCertificate(data, _verify_fn=_VerifyCertificate): |
b/lib/utils/x509.py | ||
---|---|---|
27 | 27 |
import re |
28 | 28 |
import datetime |
29 | 29 |
import calendar |
30 |
import errno |
|
31 |
import logging |
|
30 | 32 |
|
31 | 33 |
from ganeti import errors |
32 | 34 |
from ganeti import constants |
35 |
from ganeti import pathutils |
|
33 | 36 |
|
34 | 37 |
from ganeti.utils import text as utils_text |
35 | 38 |
from ganeti.utils import io as utils_io |
... | ... | |
338 | 341 |
ctx.use_certificate(cert) |
339 | 342 |
|
340 | 343 |
return ctx.check_privatekey |
344 |
|
|
345 |
|
|
346 |
def CheckNodeCertificate(cert, _noded_cert_file=pathutils.NODED_CERT_FILE): |
|
347 |
"""Checks the local node daemon certificate against given certificate. |
|
348 |
|
|
349 |
Both certificates must be signed with the same key (as stored in the local |
|
350 |
L{pathutils.NODED_CERT_FILE} file). No error is raised if no local |
|
351 |
certificate can be found. |
|
352 |
|
|
353 |
@type cert: OpenSSL.crypto.X509 |
|
354 |
@param cert: X509 certificate object |
|
355 |
@raise errors.X509CertError: When an error related to X509 occurred |
|
356 |
@raise errors.GenericError: When the verification failed |
|
357 |
|
|
358 |
""" |
|
359 |
try: |
|
360 |
noded_pem = utils_io.ReadFile(_noded_cert_file) |
|
361 |
except EnvironmentError, err: |
|
362 |
if err.errno != errno.ENOENT: |
|
363 |
raise |
|
364 |
|
|
365 |
logging.debug("Node certificate file '%s' was not found", _noded_cert_file) |
|
366 |
return |
|
367 |
|
|
368 |
try: |
|
369 |
noded_cert = \ |
|
370 |
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, noded_pem) |
|
371 |
except Exception, err: |
|
372 |
raise errors.X509CertError(_noded_cert_file, |
|
373 |
"Unable to load certificate: %s" % err) |
|
374 |
|
|
375 |
try: |
|
376 |
noded_key = \ |
|
377 |
OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, noded_pem) |
|
378 |
except Exception, err: |
|
379 |
raise errors.X509CertError(_noded_cert_file, |
|
380 |
"Unable to load private key: %s" % err) |
|
381 |
|
|
382 |
# Check consistency of server.pem file |
|
383 |
check_fn = PrepareX509CertKeyCheck(noded_cert, noded_key) |
|
384 |
try: |
|
385 |
check_fn() |
|
386 |
except OpenSSL.SSL.Error: |
|
387 |
# This should never happen as it would mean the certificate in server.pem |
|
388 |
# is out of sync with the private key stored in the same file |
|
389 |
raise errors.X509CertError(_noded_cert_file, |
|
390 |
"Certificate does not match with private key") |
|
391 |
|
|
392 |
# Check with supplied certificate with local key |
|
393 |
check_fn = PrepareX509CertKeyCheck(cert, noded_key) |
|
394 |
try: |
|
395 |
check_fn() |
|
396 |
except OpenSSL.SSL.Error: |
|
397 |
raise errors.GenericError("Given cluster certificate does not match" |
|
398 |
" local key") |
b/test/ganeti.tools.prepare_node_join_unittest.py | ||
---|---|---|
72 | 72 |
def testNoCert(self): |
73 | 73 |
prepare_node_join.VerifyCertificate({}, _verify_fn=NotImplemented) |
74 | 74 |
|
75 |
def testMismatchingKey(self): |
|
76 |
other_cert = self._TestDataFilename("cert1.pem") |
|
77 |
node_cert = self._TestDataFilename("cert2.pem") |
|
78 |
|
|
79 |
self.assertRaises(_JoinError, prepare_node_join._VerifyCertificate, |
|
80 |
utils.ReadFile(other_cert), _noded_cert_file=node_cert) |
|
81 |
|
|
82 | 75 |
def testGivenPrivateKey(self): |
83 | 76 |
cert_filename = self._TestDataFilename("cert2.pem") |
84 | 77 |
cert_pem = utils.ReadFile(cert_filename) |
85 | 78 |
|
86 | 79 |
self.assertRaises(_JoinError, prepare_node_join._VerifyCertificate, |
87 |
cert_pem, _noded_cert_file=cert_filename) |
|
88 |
|
|
89 |
def testMatchingKey(self): |
|
90 |
cert_filename = self._TestDataFilename("cert2.pem") |
|
91 |
|
|
92 |
# Extract certificate |
|
93 |
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, |
|
94 |
utils.ReadFile(cert_filename)) |
|
95 |
cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, |
|
96 |
cert) |
|
97 |
|
|
98 |
prepare_node_join._VerifyCertificate(cert_pem, |
|
99 |
_noded_cert_file=cert_filename) |
|
100 |
|
|
101 |
def testMissingFile(self): |
|
102 |
cert = self._TestDataFilename("cert1.pem") |
|
103 |
nodecert = utils.PathJoin(self.tmpdir, "does-not-exist") |
|
104 |
prepare_node_join._VerifyCertificate(utils.ReadFile(cert), |
|
105 |
_noded_cert_file=nodecert) |
|
80 |
cert_pem, _check_fn=NotImplemented) |
|
106 | 81 |
|
107 | 82 |
def testInvalidCertificate(self): |
108 | 83 |
self.assertRaises(errors.X509CertError, |
109 | 84 |
prepare_node_join._VerifyCertificate, |
110 | 85 |
"Something that's not a certificate", |
111 |
_noded_cert_file=NotImplemented)
|
|
86 |
_check_fn=NotImplemented)
|
|
112 | 87 |
|
113 |
def testNoPrivateKey(self): |
|
114 |
cert = self._TestDataFilename("cert1.pem") |
|
115 |
self.assertRaises(errors.X509CertError, |
|
116 |
prepare_node_join._VerifyCertificate, |
|
117 |
utils.ReadFile(cert), _noded_cert_file=cert) |
|
88 |
@staticmethod |
|
89 |
def _Check(cert): |
|
90 |
assert cert.get_subject() |
|
91 |
|
|
92 |
def testSuccessfulCheck(self): |
|
93 |
cert_filename = self._TestDataFilename("cert1.pem") |
|
94 |
cert_pem = utils.ReadFile(cert_filename) |
|
95 |
prepare_node_join._VerifyCertificate(cert_pem, _check_fn=self._Check) |
|
118 | 96 |
|
119 | 97 |
|
120 | 98 |
class TestVerifyClusterName(unittest.TestCase): |
b/test/ganeti.utils.x509_unittest.py | ||
---|---|---|
287 | 287 |
self.assert_(self._checkCertificate(cert1)) |
288 | 288 |
|
289 | 289 |
|
290 |
class TestCheckNodeCertificate(testutils.GanetiTestCase): |
|
291 |
def setUp(self): |
|
292 |
testutils.GanetiTestCase.setUp(self) |
|
293 |
self.tmpdir = tempfile.mkdtemp() |
|
294 |
|
|
295 |
def tearDown(self): |
|
296 |
testutils.GanetiTestCase.tearDown(self) |
|
297 |
shutil.rmtree(self.tmpdir) |
|
298 |
|
|
299 |
def testMismatchingKey(self): |
|
300 |
other_cert = self._TestDataFilename("cert1.pem") |
|
301 |
node_cert = self._TestDataFilename("cert2.pem") |
|
302 |
|
|
303 |
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, |
|
304 |
utils.ReadFile(other_cert)) |
|
305 |
|
|
306 |
try: |
|
307 |
utils.CheckNodeCertificate(cert, _noded_cert_file=node_cert) |
|
308 |
except errors.GenericError, err: |
|
309 |
self.assertEqual(str(err), |
|
310 |
"Given cluster certificate does not match local key") |
|
311 |
else: |
|
312 |
self.fail("Exception was not raised") |
|
313 |
|
|
314 |
def testMatchingKey(self): |
|
315 |
cert_filename = self._TestDataFilename("cert2.pem") |
|
316 |
|
|
317 |
# Extract certificate |
|
318 |
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, |
|
319 |
utils.ReadFile(cert_filename)) |
|
320 |
cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, |
|
321 |
cert) |
|
322 |
|
|
323 |
utils.CheckNodeCertificate(cert, _noded_cert_file=cert_filename) |
|
324 |
|
|
325 |
def testMissingFile(self): |
|
326 |
cert_path = self._TestDataFilename("cert1.pem") |
|
327 |
nodecert = utils.PathJoin(self.tmpdir, "does-not-exist") |
|
328 |
|
|
329 |
utils.CheckNodeCertificate(NotImplemented, _noded_cert_file=nodecert) |
|
330 |
|
|
331 |
self.assertFalse(os.path.exists(nodecert)) |
|
332 |
|
|
333 |
def testInvalidCertificate(self): |
|
334 |
tmpfile = utils.PathJoin(self.tmpdir, "cert") |
|
335 |
utils.WriteFile(tmpfile, data="not a certificate") |
|
336 |
|
|
337 |
self.assertRaises(errors.X509CertError, utils.CheckNodeCertificate, |
|
338 |
NotImplemented, _noded_cert_file=tmpfile) |
|
339 |
|
|
340 |
def testNoPrivateKey(self): |
|
341 |
cert = self._TestDataFilename("cert1.pem") |
|
342 |
self.assertRaises(errors.X509CertError, utils.CheckNodeCertificate, |
|
343 |
NotImplemented, _noded_cert_file=cert) |
|
344 |
|
|
345 |
def testMismatchInNodeCert(self): |
|
346 |
cert1_path = self._TestDataFilename("cert1.pem") |
|
347 |
cert2_path = self._TestDataFilename("cert2.pem") |
|
348 |
tmpfile = utils.PathJoin(self.tmpdir, "cert") |
|
349 |
|
|
350 |
# Extract certificate |
|
351 |
cert1 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, |
|
352 |
utils.ReadFile(cert1_path)) |
|
353 |
cert1_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, |
|
354 |
cert1) |
|
355 |
|
|
356 |
# Extract mismatching key |
|
357 |
key2 = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, |
|
358 |
utils.ReadFile(cert2_path)) |
|
359 |
key2_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, |
|
360 |
key2) |
|
361 |
|
|
362 |
# Write to file |
|
363 |
utils.WriteFile(tmpfile, data=cert1_pem + key2_pem) |
|
364 |
|
|
365 |
try: |
|
366 |
utils.CheckNodeCertificate(cert1, _noded_cert_file=tmpfile) |
|
367 |
except errors.X509CertError, err: |
|
368 |
self.assertEqual(err.args, |
|
369 |
(tmpfile, "Certificate does not match with private key")) |
|
370 |
else: |
|
371 |
self.fail("Exception was not raised") |
|
372 |
|
|
373 |
|
|
290 | 374 |
if __name__ == "__main__": |
291 | 375 |
testutils.GanetiTestProgram() |
Also available in: Unified diff