[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#908612: stretch-pu: package ganeti/2.15.2-7+deb9u3



Package: release.debian.org
Severity: normal
Tags: stretch
User: release.debian.org@packages.debian.org
Usertags: pu

Dear SRMs,

I'd like to update ganeti in Stretch once more, fixing the following 
issues:

 - The fix for #895599 that was included in +deb9u2 unfortunately was 
   incomplete; I failed to cherry-pick an additional patch rendering the 
   fix ineffective.

 - Ganeti uses an embedded CA to establish trust between cluster nodes.  
   This CA signs certificates using SHA-1 digests by default and 
   SHA-1-signed certificates are not acceptable by OpenSSL when a 
   security level of 2 or higher is in effect. OpenSSL in Buster will 
   (most probably) have a security level of 2 on by default, meaning 
   that upon upgrading to Buster ganeti clusters will experience 
   breakage. Since SHA-1 is weak and deprecated anyway, I would like to 
   backport a change from unstable to make the CA use SHA-256 for 
   certificate signatures, to allow cluster administrators to upgrade 
   their crypto before actually upgrading to Buster. See #907216 and 
   #907569 for more information.

 - The bash completion script shipped in Stretch is ineffective.  
   Although nothing changed on Ganeti's side between Jessie and Stretch, 
   bash completion stopped working when dh_bash-completion stopped 
   placing scripts in /etc/bash_completion.d/ (see #668254) and moved to 
   /usr/share/bash-completion/ instead. This change broke ganeti's 
   completion because it is not autoloadable: there's only one script 
   which does not match any command name (see #864755). This has already 
   been fixed in unstable by symlinking the completion file to all 
   supported command names. This being really a regression from a 
   functional point of view, I would like to backport the fix to 
   Stretch.

Attached is the full source debdiff for the proposed update. I might 
update the wording in d/NEWS and the `gnt-cluster verify' output before 
uploading, but functionally I don't expect any further changes.

Regards,
Apollon
diff -Nru ganeti-2.15.2/debian/changelog ganeti-2.15.2/debian/changelog
--- ganeti-2.15.2/debian/changelog	2018-06-11 17:42:10.000000000 +0300
+++ ganeti-2.15.2/debian/changelog	2018-09-08 20:22:03.000000000 +0300
@@ -1,3 +1,14 @@
+ganeti (2.15.2-7+deb9u3) stretch; urgency=medium
+
+  * Properly verify SSL certificates during VM export (#2) (Closes: #895599, #908112)
+  * Sign generated certificates using SHA256 instead of SHA1 (Closes: #907569)
+    + d/NEWS: ask users to run gnt-cluster renew-crypto
+    + cluster verify: warn about weak certificates
+  * Make bash completions autoloadable (Closes: #864755)
+    + Cleanup obsolete /etc/bash_completion.d/ganeti
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org>  Sat, 08 Sep 2018 20:22:03 +0300
+
 ganeti (2.15.2-7+deb9u2) stretch; urgency=medium
 
   * Properly verify SSL certificates during VM export (Closes: #895599)
diff -Nru ganeti-2.15.2/debian/ganeti.maintscript ganeti-2.15.2/debian/ganeti.maintscript
--- ganeti-2.15.2/debian/ganeti.maintscript	1970-01-01 02:00:00.000000000 +0200
+++ ganeti-2.15.2/debian/ganeti.maintscript	2018-09-08 20:22:03.000000000 +0300
@@ -0,0 +1 @@
+rm_conffile /etc/bash_completion.d/ganeti 2.15.2-7+deb9u3~
diff -Nru ganeti-2.15.2/debian/gbp.conf ganeti-2.15.2/debian/gbp.conf
--- ganeti-2.15.2/debian/gbp.conf	2018-06-11 17:42:10.000000000 +0300
+++ ganeti-2.15.2/debian/gbp.conf	2018-09-08 20:22:03.000000000 +0300
@@ -4,6 +4,8 @@
 upstream-tag = v%(version)s
 upstream-tree = tag
 upstream-branch = stable-2.15
+debian-branch = debian/stable/stretch
+dist = stretch
 
 [git-buildpackage]
 export-dir = ../build-area/
diff -Nru ganeti-2.15.2/debian/NEWS ganeti-2.15.2/debian/NEWS
--- ganeti-2.15.2/debian/NEWS	2018-06-11 17:42:10.000000000 +0300
+++ ganeti-2.15.2/debian/NEWS	2018-09-08 20:22:03.000000000 +0300
@@ -1,3 +1,27 @@
+ganeti (2.15.2-7+deb9u2) stretch; urgency=medium
+
+  This version changes Ganeti's internal CA, which is used to secure
+  intra-cluster RPC, to use SHA256 digests when signing certificates.
+  Previously issued certificates were signed using SHA1 and will be rejected
+  by newer OpenSSL versions, causing cluster malfunction. This will be a
+  problem with the upcoming Debian Buster release, so Ganeti's CA must be
+  switched over to SHA-256 before upgrading to Buster.
+
+  After upgrading all nodes to this package version, please run
+
+    gnt-cluster renew-crypto --new-cluster-certificate
+
+  at a convenient time to re-generate the cluster's certificates using the new
+  signing algorithm. This operation does not incur any instance downtime,
+  however you will not be able to issue any gnt-* commands while renew-crypto
+  is running.
+
+  If you are using built-in certificates for RAPI and/or spice, please
+  consider adding --new-rapi-certificate and --new-spice-certificate
+  respectively to the above command.
+
+ -- Apollon Oikonomopoulos <apoikos@debian.org>  Mon, 03 Sep 2018 14:36:39 +0300
+
 ganeti (2.15.2-7+deb9u1) stretch; urgency=medium
 
   This version introduces support for non-DSA SSH keys. Previously, Ganeti
diff -Nru ganeti-2.15.2/debian/patches/ca-use-sha256-md.patch ganeti-2.15.2/debian/patches/ca-use-sha256-md.patch
--- ganeti-2.15.2/debian/patches/ca-use-sha256-md.patch	1970-01-01 02:00:00.000000000 +0200
+++ ganeti-2.15.2/debian/patches/ca-use-sha256-md.patch	2018-09-08 20:22:03.000000000 +0300
@@ -0,0 +1,49 @@
+Author: Apollon Oikonomopoulos <apoikos@debian.org>
+Description: Sign generated certs using SHA256
+ Ganeti uses SHA1 digests for signed certificates, which are then rejected by
+ OpenSSL when using SECLEVEL >= 2. Since SHA1 is deprecated and considered weak
+ by several parties, we switch to using SHA256 instead.
+ .
+ While at it, drop the private definition of X509_CERT_SIGN_DIGEST from
+ utils.x509 and use the global definition from constants instead.
+Last-Update: 2018-08-27
+Bug-Debian: https://bugs.debian.org/907216
+--- a/lib/utils/x509.py
++++ b/lib/utils/x509.py
+@@ -54,7 +54,6 @@
+                             (re.escape(constants.X509_CERT_SIGNATURE_HEADER),
+                              HEX_CHAR_RE, HEX_CHAR_RE),
+                             re.S | re.I)
+-X509_CERT_SIGN_DIGEST = "SHA1"
+ 
+ # Certificate verification results
+ (CERT_WARNING,
+@@ -349,7 +348,7 @@
+   req = OpenSSL.crypto.X509Req()
+   req.get_subject().CN = common_name
+   req.set_pubkey(key_pair)
+-  req.sign(key_pair, X509_CERT_SIGN_DIGEST)
++  req.sign(key_pair, constants.X509_CERT_SIGN_DIGEST)
+ 
+   # Load the certificates used for signing.
+   signing_key = OpenSSL.crypto.load_privatekey(
+@@ -365,7 +364,7 @@
+   cert.gmtime_adj_notAfter(validity)
+   cert.set_issuer(signing_cert.get_subject())
+   cert.set_pubkey(req.get_pubkey())
+-  cert.sign(signing_key, X509_CERT_SIGN_DIGEST)
++  cert.sign(signing_key, constants.X509_CERT_SIGN_DIGEST)
+ 
+   # Encode the key and certificate in PEM format.
+   key_pem = OpenSSL.crypto.dump_privatekey(
+--- a/src/Ganeti/Constants.hs
++++ b/src/Ganeti/Constants.hs
+@@ -617,7 +617,7 @@
+ 
+ -- | Digest used to sign certificates ("openssl x509" uses SHA1 by default)
+ x509CertSignDigest :: String
+-x509CertSignDigest = "SHA1"
++x509CertSignDigest = "SHA256"
+ 
+ -- * Import/export daemon mode
+ 
diff -Nru ganeti-2.15.2/debian/patches/impexpd-fix-certificate-verification-with-new-socat-2.patch ganeti-2.15.2/debian/patches/impexpd-fix-certificate-verification-with-new-socat-2.patch
--- ganeti-2.15.2/debian/patches/impexpd-fix-certificate-verification-with-new-socat-2.patch	1970-01-01 02:00:00.000000000 +0200
+++ ganeti-2.15.2/debian/patches/impexpd-fix-certificate-verification-with-new-socat-2.patch	2018-09-08 20:22:03.000000000 +0300
@@ -0,0 +1,38 @@
+From 5f90a0f64bf5fee7fe353a95d02c79736a5943c5 Mon Sep 17 00:00:00 2001
+From: Federico Morg Pareschi <morg@google.com>
+Date: Thu, 4 Jan 2018 14:54:59 +0000
+Subject: [PATCH] Fix incorrect SOCAT_PATH constant and match typo
+
+This is a small fix to correct the previous socat change which broke
+python and tests.
+
+Signed-off-by: Federico Morg Pareschi <morg@google.com>
+---
+ lib/impexpd/__init__.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/lib/impexpd/__init__.py b/lib/impexpd/__init__.py
+index 850bdb987..cb1b9c489 100644
+--- a/lib/impexpd/__init__.py
++++ b/lib/impexpd/__init__.py
+@@ -200,7 +200,7 @@ class CommandBuilder(object):
+       # For socat versions >= 1.7.3, we need to also specify
+       # openssl-commonname, otherwise server certificate verification will
+       # fail.
+-      socat = utils.RunCmd([SOCAT_PATH, "-V"])
++      socat = utils.RunCmd([constants.SOCAT_PATH, "-V"])
+       # No need to check for errors here. If -V is not there, socat is really
+       # old. Any other failure will be handled when running the actual socat
+       # command.
+@@ -208,7 +208,7 @@ class CommandBuilder(object):
+         match = re.match(r"socat version ((\d+\.)*(\d+))", line)
+         if match:
+           try:
+-            version = tuple(int(x) for x in m.group(1).split('.'))
++            version = tuple(int(x) for x in match.group(1).split('.'))
+             if version >= (1, 7, 3):
+               addr2 += ["openssl-commonname=%s" % constants.X509_CERT_CN]
+           except TypeError:
+-- 
+2.18.0
+
diff -Nru ganeti-2.15.2/debian/patches/series ganeti-2.15.2/debian/patches/series
--- ganeti-2.15.2/debian/patches/series	2018-06-11 17:42:10.000000000 +0300
+++ ganeti-2.15.2/debian/patches/series	2018-09-08 20:22:03.000000000 +0300
@@ -17,3 +17,6 @@
 do-not-specify-socat-ssl-method.patch
 fix-failover-from-dead-node.patch
 impexpd-fix-certificate-verification-with-new-socat.patch
+impexpd-fix-certificate-verification-with-new-socat-2.patch
+ca-use-sha256-md.patch
+verify-warn-about-weak-certs.patch
diff -Nru ganeti-2.15.2/debian/patches/verify-warn-about-weak-certs.patch ganeti-2.15.2/debian/patches/verify-warn-about-weak-certs.patch
--- ganeti-2.15.2/debian/patches/verify-warn-about-weak-certs.patch	1970-01-01 02:00:00.000000000 +0200
+++ ganeti-2.15.2/debian/patches/verify-warn-about-weak-certs.patch	2018-09-08 20:22:03.000000000 +0300
@@ -0,0 +1,306 @@
+From 943270936e76648d8626e4b81f9ae14de668075f Mon Sep 17 00:00:00 2001
+From: Apollon Oikonomopoulos <apoikos@debian.org>
+Date: Mon, 3 Sep 2018 15:54:10 +0300
+Subject: [PATCH] verify: warn about weak cert keys or signing algos
+
+Extend x509.VerifyX509Certificate() to also check certificates for weak
+keys or signing algorithms. Rename _VerifyCertificateInner() to
+_VerifyX509CertificateValidity() to better match what it does, and add a
+new _VerifyX509CertificateStrength() function that checks:
+
+ - whether the public key's length is smaller than
+   constants.RSA_KEY_BITS
+ - whether the certificate is signed using a known-weak signature
+   algorithm
+
+Apart from cluster verify, VerifyX509Certificate() is also called in a
+number of places as a pre-flight check with expiration warnings
+disabled, where every non-empty response is treated as a hard error. In
+order not to break these uses, we need to change
+VerifyX509Certificate()'s API to make strength checks optional. Also we
+refactor the error handling logic to return multiple error XOR warning
+message that originate from different checks.
+
+Signed-off-by: Apollon Oikonomopoulos <apoikos@debian.org>
+---
+ lib/cmdlib/backup.py                  |  2 +-
+ lib/cmdlib/instance_create.py         |  2 +-
+ lib/utils/security.py                 |  2 +-
+ lib/utils/x509.py                     | 61 +++++++++++++++++++++--
+ src/Ganeti/Constants.hs               |  8 ++++
+ test/py/ganeti.utils.x509_unittest.py | 69 +++++++++++++++++++++------
+ 6 files changed, 122 insertions(+), 22 deletions(-)
+
+--- a/lib/cmdlib/backup.py
++++ b/lib/cmdlib/backup.py
+@@ -251,7 +251,7 @@
+         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
+                                    (err, ), errors.ECODE_INVAL)
+ 
+-      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
++      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None, False)
+       if errcode is not None:
+         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
+                                    (msg, ), errors.ECODE_INVAL)
+--- a/lib/cmdlib/instance_create.py
++++ b/lib/cmdlib/instance_create.py
+@@ -316,7 +316,7 @@
+         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
+                                    (err, ), errors.ECODE_INVAL)
+ 
+-      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
++      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None, False)
+       if errcode is not None:
+         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
+                                    errors.ECODE_INVAL)
+--- a/lib/utils/security.py
++++ b/lib/utils/security.py
+@@ -119,7 +119,7 @@
+ 
+   (errcode, msg) = \
+     x509.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
+-                               constants.SSL_CERT_EXPIRATION_ERROR)
++                               constants.SSL_CERT_EXPIRATION_ERROR, True)
+ 
+   if msg:
+     fnamemsg = "While verifying %s: %s" % (filename, msg)
+--- a/lib/utils/x509.py
++++ b/lib/utils/x509.py
+@@ -38,6 +38,8 @@
+ import calendar
+ import errno
+ import logging
++import itertools
++import operator
+ 
+ from ganeti import errors
+ from ganeti import constants
+@@ -127,8 +129,8 @@
+   return (not_before, not_after)
+ 
+ 
+-def _VerifyCertificateInner(expired, not_before, not_after, now,
+-                            warn_days, error_days):
++def _VerifyX509CertificateValidity(expired, not_before, not_after, now,
++                                   warn_days, error_days):
+   """Verifies certificate validity.
+ 
+   @type expired: bool
+@@ -178,7 +180,33 @@
+   return (None, None)
+ 
+ 
+-def VerifyX509Certificate(cert, warn_days, error_days):
++def _VerifyX509CertificateStrength(cert):
++  """Verifies the strength of a certificate
++
++  @type sig_algo: string
++  @param sig_algo: Name of the algorithm used to sign the certificate
++  @type key_bits: int
++  @param key_bits: Length of the public/private key in bits
++  """
++  sig_algo = cert.get_signature_algorithm()
++  key_bits = cert.get_pubkey().bits()
++
++  warnings = []
++  if sig_algo in constants.X509_WEAK_SIGNATURE_ALGORITHMS:
++    warnings.append("weak signature algorithm '%s'" % sig_algo)
++
++  if key_bits < constants.RSA_KEY_BITS:
++    warnings.append("weak public/private keypair: %d bits,"
++                    " should be at least %d bits" %
++                    (key_bits, constants.RSA_KEY_BITS))
++
++  if warnings:
++    warnings.append("see /usr/share/doc/ganeti/NEWS.Debian.gz")
++    return (CERT_WARNING, ", ".join(warnings))
++  return (None, None)
++
++
++def VerifyX509Certificate(cert, warn_days, error_days, check_strength):
+   """Verifies a certificate for LUClusterVerify.
+ 
+   @type cert: OpenSSL.crypto.X509
+@@ -187,6 +215,8 @@
+   @param warn_days: How many days before expiration a warning should be reported
+   @type error_days: number or None
+   @param error_days: How many days before expiration an error should be reported
++  @type check_strength: bool
++  @param check_strength: Whether to check the certificate's strength
+ 
+   """
+   # Depending on the pyOpenSSL version, this can just return (None, None)
+@@ -194,8 +224,28 @@
+ 
+   now = time.time() + constants.NODE_MAX_CLOCK_SKEW
+ 
+-  return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
+-                                 now, warn_days, error_days)
++  checks = []
++  checks.append(_VerifyX509CertificateValidity(cert.has_expired(), not_before,
++                                               not_after, now, warn_days,
++                                               error_days))
++
++  if check_strength:
++    checks.append(_VerifyX509CertificateStrength(cert))
++
++
++  keyfunc = operator.itemgetter(0)
++
++  # Drop non-error results
++  checks = [c for c in checks if keyfunc(c) is not None]
++
++  if not checks:
++    return (None, None)
++
++  # Return all errors of the most severe level
++  for level, errors in itertools.groupby(sorted(checks, key=keyfunc,
++                                                reverse=True),
++                                                keyfunc):
++    return (level, ", ".join(e[1] for e in errors))
+ 
+ 
+ def SignX509Certificate(cert, key, salt):
+--- a/src/Ganeti/Constants.hs
++++ b/src/Ganeti/Constants.hs
+@@ -619,6 +619,14 @@
+ x509CertSignDigest :: String
+ x509CertSignDigest = "SHA256"
+ 
++-- | Known-weak certificate signature algorithms
++x509SignatureAlgoSha1 :: String
++x509SignatureAlgoSha1 = "sha1WithRSAEncryption"
++
++x509WeakSignatureAlgorithms :: FrozenSet String
++x509WeakSignatureAlgorithms = ConstantUtils.mkSet [x509SignatureAlgoSha1]
++
++
+ -- * Import/export daemon mode
+ 
+ iemExport :: String
+--- a/test/py/ganeti.utils.x509_unittest.py
++++ b/test/py/ganeti.utils.x509_unittest.py
+@@ -159,9 +159,13 @@
+     testutils.GanetiTestCase.setUp(self)
+ 
+     self.tmpdir = tempfile.mkdtemp()
++    self.orig_rsa_key_bits = constants.RSA_KEY_BITS
++    self.orig_x509_weak_signature_algorithms = constants.X509_WEAK_SIGNATURE_ALGORITHMS
+ 
+   def tearDown(self):
+     shutil.rmtree(self.tmpdir)
++    constants.RSA_KEY_BITS = self.orig_rsa_key_bits
++    constants.X509_WEAK_SIGNATURE_ALGORITHMS = self.orig_x509_weak_signature_algorithms
+ 
+   def testVerifyCertificate(self):
+     cert_pem = testutils.ReadTestData("cert1.pem")
+@@ -169,7 +173,7 @@
+                                            cert_pem)
+ 
+     # Not checking return value as this certificate is expired
+-    utils.VerifyX509Certificate(cert, 30, 7)
++    utils.VerifyX509Certificate(cert, 30, 7, True)
+ 
+   @staticmethod
+   def _GenCert(key, before, validity):
+@@ -198,50 +202,87 @@
+     # few lines take more than NODE_MAX_CLOCK_SKEW / 2
+     for before in [-1, 0, SKEW / 4, SKEW / 2]:
+       cert = self._GenCert(key, before, validity)
+-      result = utils.VerifyX509Certificate(cert, 1, 2)
++      result = utils.VerifyX509Certificate(cert, 1, 2, True)
+       self.assertEqual(result, (None, None))
+ 
+     # skew too great, not accepting certs
+     for before in [SKEW * 2, SKEW * 10]:
+       cert = self._GenCert(key, before, validity)
+-      (status, msg) = utils.VerifyX509Certificate(cert, 1, 2)
++      (status, msg) = utils.VerifyX509Certificate(cert, 1, 2, True)
+       self.assertEqual(status, utils.CERT_WARNING)
+       self.assertTrue(msg.startswith("Certificate not yet valid"))
+ 
++  def testKeyStrength(self):
++    # Create private and public key
++    key = OpenSSL.crypto.PKey()
++    key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
++
++    validity = 7 * 86400
++    cert = self._GenCert(key, 0, validity)
++
++    result = utils.VerifyX509Certificate(cert, None, None, True)
++    self.assertEqual(result, (None, None))
++
++    constants.RSA_KEY_BITS *= 2
++    status, msg = utils.VerifyX509Certificate(cert, None, None, True)
++    self.assertTrue(status == utils.CERT_WARNING)
++    self.assertTrue(msg.startswith("weak public/private key"))
++    constants.RSA_KEY_BITS = self.orig_rsa_key_bits
++
++  def testSigningAlgoStrength(self):
++    # Create private and public key
++    key = OpenSSL.crypto.PKey()
++    key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
++
++    validity = 7 * 86400
++    cert = self._GenCert(key, 0, validity)
++
++    result = utils.VerifyX509Certificate(cert, None, None, True)
++    self.assertEqual(result, (None, None))
++
++    signing_algo = cert.get_signature_algorithm()
++
++    constants.X509_WEAK_SIGNATURE_ALGORITHMS = \
++        constants.X509_WEAK_SIGNATURE_ALGORITHMS.union([signing_algo])
++    status, msg = utils.VerifyX509Certificate(cert, None, None, True)
++    self.assertTrue(status == utils.CERT_WARNING)
++    self.assertTrue(msg.startswith("weak signature algorithm"))
++    constants.X509_WEAK_SIGNATURE_ALGORITHMS = self.orig_x509_weak_signature_algorithms
++
+ 
+-class TestVerifyCertificateInner(unittest.TestCase):
++class TestVerifyX509CertificateValidity(unittest.TestCase):
+   def test(self):
+-    vci = utils.x509._VerifyCertificateInner
++    vcv = utils.x509._VerifyX509CertificateValidity
+ 
+     # Valid
+-    self.assertEqual(vci(False, 1263916313, 1298476313, 1266940313, 30, 7),
++    self.assertEqual(vcv(False, 1263916313, 1298476313, 1266940313, 30, 7),
+                      (None, None))
+ 
+     # Not yet valid
+-    (errcode, msg) = vci(False, 1266507600, 1267544400, 1266075600, 30, 7)
++    (errcode, msg) = vcv(False, 1266507600, 1267544400, 1266075600, 30, 7)
+     self.assertEqual(errcode, utils.CERT_WARNING)
+ 
+     # Expiring soon
+-    (errcode, msg) = vci(False, 1266507600, 1267544400, 1266939600, 30, 7)
++    (errcode, msg) = vcv(False, 1266507600, 1267544400, 1266939600, 30, 7)
+     self.assertEqual(errcode, utils.CERT_ERROR)
+ 
+-    (errcode, msg) = vci(False, 1266507600, 1267544400, 1266939600, 30, 1)
++    (errcode, msg) = vcv(False, 1266507600, 1267544400, 1266939600, 30, 1)
+     self.assertEqual(errcode, utils.CERT_WARNING)
+ 
+-    (errcode, msg) = vci(False, 1266507600, None, 1266939600, 30, 7)
++    (errcode, msg) = vcv(False, 1266507600, None, 1266939600, 30, 7)
+     self.assertEqual(errcode, None)
+ 
+     # Expired
+-    (errcode, msg) = vci(True, 1266507600, 1267544400, 1266939600, 30, 7)
++    (errcode, msg) = vcv(True, 1266507600, 1267544400, 1266939600, 30, 7)
+     self.assertEqual(errcode, utils.CERT_ERROR)
+ 
+-    (errcode, msg) = vci(True, None, 1267544400, 1266939600, 30, 7)
++    (errcode, msg) = vcv(True, None, 1267544400, 1266939600, 30, 7)
+     self.assertEqual(errcode, utils.CERT_ERROR)
+ 
+-    (errcode, msg) = vci(True, 1266507600, None, 1266939600, 30, 7)
++    (errcode, msg) = vcv(True, 1266507600, None, 1266939600, 30, 7)
+     self.assertEqual(errcode, utils.CERT_ERROR)
+ 
+-    (errcode, msg) = vci(True, None, None, 1266939600, 30, 7)
++    (errcode, msg) = vcv(True, None, None, 1266939600, 30, 7)
+     self.assertEqual(errcode, utils.CERT_ERROR)
+ 
+ 
diff -Nru ganeti-2.15.2/debian/rules ganeti-2.15.2/debian/rules
--- ganeti-2.15.2/debian/rules	2018-06-11 17:42:10.000000000 +0300
+++ ganeti-2.15.2/debian/rules	2018-09-08 20:22:03.000000000 +0300
@@ -147,6 +147,13 @@
 
 	# Now let dh_link fix all symlinks
 	dh_link
+	
+	# Make bash completion autoloadable
+	for script in $$(grep ^complete $(CURDIR)/debian/ganeti/usr/share/bash-completion/completions/ganeti \
+		| awk '/ +[a-z_-]+$$/ { print $$NF }') ; do \
+		dh_link -pganeti "usr/share/bash-completion/completions/ganeti" \
+			         "usr/share/bash-completion/completions/$$script" ; \
+	done
 
 # Disable dh_sphinxdoc for binary-arch, as it will raise an error
 override_dh_sphinxdoc-arch:

Reply to: