Package: release.debian.org Severity: normal Tags: stretch User: release.debian....@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 <apoi...@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 <apoi...@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 <apoi...@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 <m...@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 <m...@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 <apoi...@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 <apoi...@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: