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