Revision 24d70417

b/lib/cmdlib.py
923 923
  return faulty
924 924

  
925 925

  
926
def _FormatTimestamp(secs):
927
  """Formats a Unix timestamp with the local timezone.
928

  
929
  """
930
  return time.strftime("%F %T %Z", time.gmtime(secs))
931

  
932

  
933 926
class LUPostInitCluster(LogicalUnit):
934 927
  """Logical unit for running hooks after cluster initialization.
935 928

  
......
1021 1014
    return master
1022 1015

  
1023 1016

  
1024
def _VerifyCertificateInner(filename, expired, not_before, not_after, now,
1025
                            warn_days=constants.SSL_CERT_EXPIRATION_WARN,
1026
                            error_days=constants.SSL_CERT_EXPIRATION_ERROR):
1027
  """Verifies certificate details for LUVerifyCluster.
1028

  
1029
  """
1030
  if expired:
1031
    msg = "Certificate %s is expired" % filename
1032

  
1033
    if not_before is not None and not_after is not None:
1034
      msg += (" (valid from %s to %s)" %
1035
              (_FormatTimestamp(not_before),
1036
               _FormatTimestamp(not_after)))
1037
    elif not_before is not None:
1038
      msg += " (valid from %s)" % _FormatTimestamp(not_before)
1039
    elif not_after is not None:
1040
      msg += " (valid until %s)" % _FormatTimestamp(not_after)
1041

  
1042
    return (LUVerifyCluster.ETYPE_ERROR, msg)
1043

  
1044
  elif not_before is not None and not_before > now:
1045
    return (LUVerifyCluster.ETYPE_WARNING,
1046
            "Certificate %s not yet valid (valid from %s)" %
1047
            (filename, _FormatTimestamp(not_before)))
1048

  
1049
  elif not_after is not None:
1050
    remaining_days = int((not_after - now) / (24 * 3600))
1051

  
1052
    msg = ("Certificate %s expires in %d days" % (filename, remaining_days))
1053

  
1054
    if remaining_days <= error_days:
1055
      return (LUVerifyCluster.ETYPE_ERROR, msg)
1056

  
1057
    if remaining_days <= warn_days:
1058
      return (LUVerifyCluster.ETYPE_WARNING, msg)
1059

  
1060
  return (None, None)
1061

  
1062

  
1063 1017
def _VerifyCertificate(filename):
1064 1018
  """Verifies a certificate for LUVerifyCluster.
1065 1019

  
......
1074 1028
    return (LUVerifyCluster.ETYPE_ERROR,
1075 1029
            "Failed to load X509 certificate %s: %s" % (filename, err))
1076 1030

  
1077
  # Depending on the pyOpenSSL version, this can just return (None, None)
1078
  (not_before, not_after) = utils.GetX509CertValidity(cert)
1031
  (errcode, msg) = \
1032
    utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1033
                                constants.SSL_CERT_EXPIRATION_ERROR)
1034

  
1035
  if msg:
1036
    fnamemsg = "While verifying %s: %s" % (filename, msg)
1037
  else:
1038
    fnamemsg = None
1039

  
1040
  if errcode is None:
1041
    return (None, fnamemsg)
1042
  elif errcode == utils.CERT_WARNING:
1043
    return (LUVerifyCluster.ETYPE_WARNING, fnamemsg)
1044
  elif errcode == utils.CERT_ERROR:
1045
    return (LUVerifyCluster.ETYPE_ERROR, fnamemsg)
1079 1046

  
1080
  return _VerifyCertificateInner(filename, cert.has_expired(),
1081
                                 not_before, not_after, time.time())
1047
  raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1082 1048

  
1083 1049

  
1084 1050
class LUVerifyCluster(LogicalUnit):
b/lib/utils.py
92 92
_STRUCT_UCRED = "iII"
93 93
_STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
94 94

  
95
# Certificate verification results
96
(CERT_WARNING,
97
 CERT_ERROR) = range(1, 3)
98

  
95 99

  
96 100
class RunResult(object):
97 101
  """Holds the result of running external programs.
......
2442 2446
  return rows[-lines:]
2443 2447

  
2444 2448

  
2449
def FormatTimestampWithTZ(secs):
2450
  """Formats a Unix timestamp with the local timezone.
2451

  
2452
  """
2453
  return time.strftime("%F %T %Z", time.gmtime(secs))
2454

  
2455

  
2445 2456
def _ParseAsn1Generalizedtime(value):
2446 2457
  """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2447 2458

  
......
2505 2516
  return (not_before, not_after)
2506 2517

  
2507 2518

  
2519
def _VerifyCertificateInner(expired, not_before, not_after, now,
2520
                            warn_days, error_days):
2521
  """Verifies certificate validity.
2522

  
2523
  @type expired: bool
2524
  @param expired: Whether pyOpenSSL considers the certificate as expired
2525
  @type not_before: number or None
2526
  @param not_before: Unix timestamp before which certificate is not valid
2527
  @type not_after: number or None
2528
  @param not_after: Unix timestamp after which certificate is invalid
2529
  @type now: number
2530
  @param now: Current time as Unix timestamp
2531
  @type warn_days: number or None
2532
  @param warn_days: How many days before expiration a warning should be reported
2533
  @type error_days: number or None
2534
  @param error_days: How many days before expiration an error should be reported
2535

  
2536
  """
2537
  if expired:
2538
    msg = "Certificate is expired"
2539

  
2540
    if not_before is not None and not_after is not None:
2541
      msg += (" (valid from %s to %s)" %
2542
              (FormatTimestampWithTZ(not_before),
2543
               FormatTimestampWithTZ(not_after)))
2544
    elif not_before is not None:
2545
      msg += " (valid from %s)" % FormatTimestampWithTZ(not_before)
2546
    elif not_after is not None:
2547
      msg += " (valid until %s)" % FormatTimestampWithTZ(not_after)
2548

  
2549
    return (CERT_ERROR, msg)
2550

  
2551
  elif not_before is not None and not_before > now:
2552
    return (CERT_WARNING,
2553
            "Certificate not yet valid (valid from %s)" %
2554
            FormatTimestampWithTZ(not_before))
2555

  
2556
  elif not_after is not None:
2557
    remaining_days = int((not_after - now) / (24 * 3600))
2558

  
2559
    msg = "Certificate expires in about %d days" % remaining_days
2560

  
2561
    if error_days is not None and remaining_days <= error_days:
2562
      return (CERT_ERROR, msg)
2563

  
2564
    if warn_days is not None and remaining_days <= warn_days:
2565
      return (CERT_WARNING, msg)
2566

  
2567
  return (None, None)
2568

  
2569

  
2570
def VerifyX509Certificate(cert, warn_days, error_days):
2571
  """Verifies a certificate for LUVerifyCluster.
2572

  
2573
  @type cert: OpenSSL.crypto.X509
2574
  @param cert: X509 certificate object
2575
  @type warn_days: number or None
2576
  @param warn_days: How many days before expiration a warning should be reported
2577
  @type error_days: number or None
2578
  @param error_days: How many days before expiration an error should be reported
2579

  
2580
  """
2581
  # Depending on the pyOpenSSL version, this can just return (None, None)
2582
  (not_before, not_after) = GetX509CertValidity(cert)
2583

  
2584
  return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
2585
                                 time.time(), warn_days, error_days)
2586

  
2587

  
2508 2588
def SignX509Certificate(cert, key, salt):
2509 2589
  """Sign a X509 certificate.
2510 2590

  
b/test/ganeti.cmdlib_unittest.py
52 52
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
53 53

  
54 54
    # Try to load non-certificate file
55
    invalid_cert = self._TestDataFilename("bdev-net1.txt")
55
    invalid_cert = self._TestDataFilename("bdev-net.txt")
56 56
    (errcode, msg) = cmdlib._VerifyCertificate(invalid_cert)
57 57
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
58 58

  
59 59

  
60
class TestVerifyCertificateInner(unittest.TestCase):
61
  FAKEFILE = "/tmp/fake/cert/file.pem"
62

  
63
  def test(self):
64
    vci = cmdlib._VerifyCertificateInner
65

  
66
    # Valid
67
    self.assertEqual(vci(self.FAKEFILE, False, 1263916313, 1298476313,
68
                         1266940313, warn_days=30, error_days=7),
69
                     (None, None))
70

  
71
    # Not yet valid
72
    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, 1267544400,
73
                         1266075600, warn_days=30, error_days=7)
74
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_WARNING)
75

  
76
    # Expiring soon
77
    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, 1267544400,
78
                         1266939600, warn_days=30, error_days=7)
79
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
80

  
81
    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, 1267544400,
82
                         1266939600, warn_days=30, error_days=1)
83
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_WARNING)
84

  
85
    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, None,
86
                         1266939600, warn_days=30, error_days=7)
87
    self.assertEqual(errcode, None)
88

  
89
    # Expired
90
    (errcode, msg) = vci(self.FAKEFILE, True, 1266507600, 1267544400,
91
                         1266939600, warn_days=30, error_days=7)
92
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
93

  
94
    (errcode, msg) = vci(self.FAKEFILE, True, None, 1267544400,
95
                         1266939600, warn_days=30, error_days=7)
96
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
97

  
98
    (errcode, msg) = vci(self.FAKEFILE, True, 1266507600, None,
99
                         1266939600, warn_days=30, error_days=7)
100
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
101

  
102
    (errcode, msg) = vci(self.FAKEFILE, True, None, None,
103
                         1266939600, warn_days=30, error_days=7)
104
    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
105

  
106

  
107 60
if __name__ == "__main__":
108 61
  testutils.GanetiTestProgram()
b/test/ganeti.utils_unittest.py
1945 1945
    self.assertRaises(EnvironmentError, utils.ReadLockedPidFile, path)
1946 1946

  
1947 1947

  
1948
class TestCertVerification(testutils.GanetiTestCase):
1949
  def setUp(self):
1950
    testutils.GanetiTestCase.setUp(self)
1951

  
1952
    self.tmpdir = tempfile.mkdtemp()
1953

  
1954
  def tearDown(self):
1955
    shutil.rmtree(self.tmpdir)
1956

  
1957
  def testVerifyCertificate(self):
1958
    cert_pem = utils.ReadFile(self._TestDataFilename("cert1.pem"))
1959
    cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1960
                                           cert_pem)
1961

  
1962
    # Not checking return value as this certificate is expired
1963
    utils.VerifyX509Certificate(cert, 30, 7)
1964

  
1965

  
1966
class TestVerifyCertificateInner(unittest.TestCase):
1967
  def test(self):
1968
    vci = utils._VerifyCertificateInner
1969

  
1970
    # Valid
1971
    self.assertEqual(vci(False, 1263916313, 1298476313, 1266940313, 30, 7),
1972
                     (None, None))
1973

  
1974
    # Not yet valid
1975
    (errcode, msg) = vci(False, 1266507600, 1267544400, 1266075600, 30, 7)
1976
    self.assertEqual(errcode, utils.CERT_WARNING)
1977

  
1978
    # Expiring soon
1979
    (errcode, msg) = vci(False, 1266507600, 1267544400, 1266939600, 30, 7)
1980
    self.assertEqual(errcode, utils.CERT_ERROR)
1981

  
1982
    (errcode, msg) = vci(False, 1266507600, 1267544400, 1266939600, 30, 1)
1983
    self.assertEqual(errcode, utils.CERT_WARNING)
1984

  
1985
    (errcode, msg) = vci(False, 1266507600, None, 1266939600, 30, 7)
1986
    self.assertEqual(errcode, None)
1987

  
1988
    # Expired
1989
    (errcode, msg) = vci(True, 1266507600, 1267544400, 1266939600, 30, 7)
1990
    self.assertEqual(errcode, utils.CERT_ERROR)
1991

  
1992
    (errcode, msg) = vci(True, None, 1267544400, 1266939600, 30, 7)
1993
    self.assertEqual(errcode, utils.CERT_ERROR)
1994

  
1995
    (errcode, msg) = vci(True, 1266507600, None, 1266939600, 30, 7)
1996
    self.assertEqual(errcode, utils.CERT_ERROR)
1997

  
1998
    (errcode, msg) = vci(True, None, None, 1266939600, 30, 7)
1999
    self.assertEqual(errcode, utils.CERT_ERROR)
2000

  
2001

  
1948 2002
if __name__ == '__main__':
1949 2003
  testutils.GanetiTestProgram()

Also available in: Unified diff