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