From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
To: openembedded-core@lists.openembedded.org
Subject: [PATCH 2/3] New lib module for handling GPG signing
Date: Mon, 11 Jan 2016 18:13:18 +0200 [thread overview]
Message-ID: <1452528799-11292-3-git-send-email-markus.lehtonen@linux.intel.com> (raw)
In-Reply-To: <1452528799-11292-1-git-send-email-markus.lehtonen@linux.intel.com>
Add a new Python module (oe.gpg_sign) for handling GPG signing
operations, i.e. currently package and package feed signing. The
purpose is to be able to more easily support various signing backends.
Currently, only local on-the-build-host signing is implemented.
[YOCTO #8755]
Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
---
meta/classes/sign_package_feed.bbclass | 6 +++
meta/classes/sign_rpm.bbclass | 47 +++++----------------
meta/lib/oe/gpg_sign.py | 76 ++++++++++++++++++++++++++++++++++
meta/lib/oe/package_manager.py | 31 +++++---------
meta/recipes-core/meta/signing-keys.bb | 26 ++++++------
5 files changed, 116 insertions(+), 70 deletions(-)
create mode 100644 meta/lib/oe/gpg_sign.py
diff --git a/meta/classes/sign_package_feed.bbclass b/meta/classes/sign_package_feed.bbclass
index d89bc0b..d5df8af 100644
--- a/meta/classes/sign_package_feed.bbclass
+++ b/meta/classes/sign_package_feed.bbclass
@@ -6,6 +6,10 @@
# Path to a file containing the passphrase of the signing key.
# PACKAGE_FEED_GPG_NAME
# Name of the key to sign with. May be key id or key name.
+# PACKAGE_FEED_GPG_BACKEND
+# Optional variable for specifying the backend to use for signing.
+# Currently the only available option is 'local', i.e. local signing
+# on the build host.
# GPG_BIN
# Optional variable for specifying the gpg binary/wrapper to use for
# signing.
@@ -15,6 +19,8 @@
inherit sanity
PACKAGE_FEED_SIGN = '1'
+PACKAGE_FEED_GPG_BACKEND ?= 'local'
+
python () {
# Check sanity of configuration
diff --git a/meta/classes/sign_rpm.bbclass b/meta/classes/sign_rpm.bbclass
index 7906b64..8bcabee 100644
--- a/meta/classes/sign_rpm.bbclass
+++ b/meta/classes/sign_rpm.bbclass
@@ -5,6 +5,10 @@
# Path to a file containing the passphrase of the signing key.
# RPM_GPG_NAME
# Name of the key to sign with. May be key id or key name.
+# RPM_GPG_BACKEND
+# Optional variable for specifying the backend to use for signing.
+# Currently the only available option is 'local', i.e. local signing
+# on the build host.
# GPG_BIN
# Optional variable for specifying the gpg binary/wrapper to use for
# signing.
@@ -14,6 +18,7 @@
inherit sanity
RPM_SIGN_PACKAGES='1'
+RPM_GPG_BACKEND ?= 'local'
python () {
@@ -27,47 +32,17 @@ python () {
'RPM-GPG-PUBKEY'))
}
-
-def rpmsign_wrapper(d, files, passphrase, gpg_name=None):
- import pexpect
-
- # Find the correct rpm binary
- rpm_bin_path = d.getVar('STAGING_BINDIR_NATIVE', True) + '/rpm'
- cmd = rpm_bin_path + " --addsign --define '_gpg_name %s' " % gpg_name
- if d.getVar('GPG_BIN', True):
- cmd += "--define '%%__gpg %s' " % d.getVar('GPG_BIN', True)
- if d.getVar('GPG_PATH', True):
- cmd += "--define '_gpg_path %s' " % d.getVar('GPG_PATH', True)
- cmd += ' '.join(files)
-
- # Need to use pexpect for feeding the passphrase
- proc = pexpect.spawn(cmd)
- try:
- proc.expect_exact('Enter pass phrase:', timeout=15)
- proc.sendline(passphrase)
- proc.expect(pexpect.EOF, timeout=900)
- proc.close()
- except pexpect.TIMEOUT as err:
- bb.warn('rpmsign timeout: %s' % err)
- proc.terminate()
- else:
- if os.WEXITSTATUS(proc.status) or not os.WIFEXITED(proc.status):
- bb.warn('rpmsign failed: %s' % proc.before.strip())
- return proc.exitstatus
-
-
python sign_rpm () {
import glob
+ from oe.gpg_sign import get_signer
- with open(d.getVar("RPM_GPG_PASSPHRASE_FILE", True)) as fobj:
- rpm_gpg_passphrase = fobj.readlines()[0].rstrip('\n')
-
- rpm_gpg_name = (d.getVar("RPM_GPG_NAME", True) or "")
-
+ signer = get_signer(d,
+ d.getVar('RPM_GPG_BACKEND', True),
+ d.getVar('RPM_GPG_NAME', True),
+ d.getVar('RPM_GPG_PASSPHRASE_FILE', True))
rpms = glob.glob(d.getVar('RPM_PKGWRITEDIR', True) + '/*')
- if rpmsign_wrapper(d, rpms, rpm_gpg_passphrase, rpm_gpg_name) != 0:
- raise bb.build.FuncFailed("RPM signing failed")
+ signer.sign_rpms(rpms)
}
do_package_index[depends] += "signing-keys:do_export_public_keys"
diff --git a/meta/lib/oe/gpg_sign.py b/meta/lib/oe/gpg_sign.py
new file mode 100644
index 0000000..55abad8
--- /dev/null
+++ b/meta/lib/oe/gpg_sign.py
@@ -0,0 +1,76 @@
+"""Helper module for GPG signing"""
+import os
+
+import bb
+import oe.utils
+
+class LocalSigner(object):
+ """Class for handling local (on the build host) signing"""
+ def __init__(self, d, keyid, passphrase_file):
+ self.keyid = keyid
+ self.passphrase_file = passphrase_file
+ self.gpg_bin = d.getVar('GPG_BIN', True) or \
+ bb.utils.which(os.getenv('PATH'), 'gpg')
+ self.gpg_path = d.getVar('GPG_PATH', True)
+ self.rpm_bin = bb.utils.which(os.getenv('PATH'), "rpm")
+
+ def export_pubkey(self, output_file):
+ """Export GPG public key to a file"""
+ cmd = '%s --batch --yes --export --armor -o %s ' % \
+ (self.gpg_bin, output_file)
+ if self.gpg_path:
+ cmd += "--homedir %s " % self.gpg_path
+ cmd += self.keyid
+ status, output = oe.utils.getstatusoutput(cmd)
+ if status:
+ raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' %
+ (self.keyid, output))
+
+ def sign_rpms(self, files):
+ """Sign RPM files"""
+ import pexpect
+
+ cmd = self.rpm_bin + " --addsign --define '_gpg_name %s' " % self.keyid
+ if self.gpg_bin:
+ cmd += "--define '%%__gpg %s' " % self.gpg_bin
+ if self.gpg_path:
+ cmd += "--define '_gpg_path %s' " % self.gpg_path
+ cmd += ' '.join(files)
+
+ # Need to use pexpect for feeding the passphrase
+ proc = pexpect.spawn(cmd)
+ try:
+ proc.expect_exact('Enter pass phrase:', timeout=15)
+ with open(self.passphrase_file) as fobj:
+ proc.sendline(fobj.readline().rstrip('\n'))
+ proc.expect(pexpect.EOF, timeout=900)
+ proc.close()
+ except pexpect.TIMEOUT as err:
+ bb.error('rpmsign timeout: %s' % err)
+ proc.terminate()
+ if os.WEXITSTATUS(proc.status) or not os.WIFEXITED(proc.status):
+ bb.error('rpmsign failed: %s' % proc.before.strip())
+ raise bb.build.FuncFailed("Failed to sign RPM packages")
+
+ def detach_sign(self, input_file):
+ """Create a detached signature of a file"""
+ cmd = "%s --detach-sign --armor --batch --no-tty --yes " \
+ "--passphrase-file '%s' -u '%s' " % \
+ (self.gpg_bin, self.passphrase_file, self.keyid)
+ if self.gpg_path:
+ gpg_cmd += "--homedir %s " % self.gpg_path
+ cmd += input_file
+ status, output = oe.utils.getstatusoutput(cmd)
+ if status:
+ raise bb.build.FuncFailed("Failed to create signature for '%s': %s" %
+ (input_file, output))
+
+
+def get_signer(d, backend, keyid, passphrase_file):
+ """Get signer object for the specified backend"""
+ # Use local signing by default
+ if backend == 'local':
+ return LocalSigner(d, keyid, passphrase_file)
+ else:
+ bb.fatal("Unsupported signing backend '%s'" % backend)
+
diff --git a/meta/lib/oe/package_manager.py b/meta/lib/oe/package_manager.py
index 32afeaf..c7df4bc 100644
--- a/meta/lib/oe/package_manager.py
+++ b/meta/lib/oe/package_manager.py
@@ -9,6 +9,7 @@ import bb
import tempfile
import oe.utils
import string
+from oe.gpg_sign import get_signer
# this can be used by all PM backends to create the index files in parallel
def create_index(arg):
@@ -109,16 +110,14 @@ class RpmIndexer(Indexer):
rpm_createrepo = bb.utils.which(os.getenv('PATH'), "createrepo")
if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1':
- pkgfeed_gpg_name = self.d.getVar('PACKAGE_FEED_GPG_NAME', True)
- pkgfeed_gpg_pass = self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)
+ signer = get_signer(self.d,
+ self.d.getVar('PACKAGE_FEED_GPG_BACKEND', True),
+ self.d.getVar('PACKAGE_FEED_GPG_NAME', True),
+ self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True))
else:
- pkgfeed_gpg_name = None
- pkgfeed_gpg_pass = None
- gpg_bin = self.d.getVar('GPG_BIN', True) or \
- bb.utils.which(os.getenv('PATH'), "gpg")
-
+ signer = None
index_cmds = []
- repo_sign_cmds = []
+ repomd_files = []
rpm_dirs_found = False
for arch in archs:
dbpath = os.path.join(self.d.getVar('WORKDIR', True), 'rpmdb', arch)
@@ -130,15 +129,7 @@ class RpmIndexer(Indexer):
index_cmds.append("%s --dbpath %s --update -q %s" % \
(rpm_createrepo, dbpath, arch_dir))
- if pkgfeed_gpg_name:
- repomd_file = os.path.join(arch_dir, 'repodata', 'repomd.xml')
- gpg_cmd = "%s --detach-sign --armor --batch --no-tty --yes " \
- "--passphrase-file '%s' -u '%s' " % \
- (gpg_bin, pkgfeed_gpg_pass, pkgfeed_gpg_name)
- if self.d.getVar('GPG_PATH', True):
- gpg_cmd += "--homedir %s " % self.d.getVar('GPG_PATH', True)
- gpg_cmd += repomd_file
- repo_sign_cmds.append(gpg_cmd)
+ repomd_files.append(os.path.join(arch_dir, 'repodata', 'repomd.xml'))
rpm_dirs_found = True
@@ -151,9 +142,9 @@ class RpmIndexer(Indexer):
if result:
bb.fatal('%s' % ('\n'.join(result)))
# Sign repomd
- result = oe.utils.multiprocess_exec(repo_sign_cmds, create_index)
- if result:
- bb.fatal('%s' % ('\n'.join(result)))
+ if signer:
+ for repomd in repomd_files:
+ signer.detach_sign(repomd)
# Copy pubkey(s) to repo
distro_version = self.d.getVar('DISTRO_VERSION', True) or "oe.0"
if self.d.getVar('RPM_SIGN_PACKAGES', True) == '1':
diff --git a/meta/recipes-core/meta/signing-keys.bb b/meta/recipes-core/meta/signing-keys.bb
index cc401f3..d7aa79d 100644
--- a/meta/recipes-core/meta/signing-keys.bb
+++ b/meta/recipes-core/meta/signing-keys.bb
@@ -20,26 +20,24 @@ do_populate_sysroot[noexec] = "1"
EXCLUDE_FROM_WORLD = "1"
-def export_gpg_pubkey(d, keyid, path):
- import bb
- gpg_bin = d.getVar('GPG_BIN', True) or \
- bb.utils.which(os.getenv('PATH'), "gpg")
- cmd = '%s --batch --yes --export --armor -o %s %s' % \
- (gpg_bin, path, keyid)
- status, output = oe.utils.getstatusoutput(cmd)
- if status:
- raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' %
- (keyid, output))
python do_export_public_keys () {
+ from oe.gpg_sign import get_signer
+
if d.getVar("RPM_SIGN_PACKAGES", True):
# Export public key of the rpm signing key
- export_gpg_pubkey(d, d.getVar("RPM_GPG_NAME", True),
- d.getVar('RPM_GPG_PUBKEY', True))
+ signer = get_signer(d,
+ d.getVar('RPM_GPG_BACKEND', True),
+ d.getVar('RPM_GPG_NAME', True),
+ d.getVar('RPM_GPG_PASSPHRASE_FILE', True))
+ signer.export_pubkey(d.getVar('RPM_GPG_PUBKEY', True))
if d.getVar('PACKAGE_FEED_SIGN', True) == '1':
# Export public key of the feed signing key
- export_gpg_pubkey(d, d.getVar("PACKAGE_FEED_GPG_NAME", True),
- d.getVar('PACKAGE_FEED_GPG_PUBKEY', True))
+ signer = get_signer(d,
+ d.getVar('PACKAGE_FEED_GPG_BACKEND', True),
+ d.getVar('PACKAGE_FEED_GPG_NAME', True),
+ d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True))
+ signer.export_pubkey(d.getVar('PACKAGE_FEED_GPG_PUBKEY', True))
}
addtask do_export_public_keys before do_build
--
2.1.4
next prev parent reply other threads:[~2016-01-11 16:13 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-01-11 16:13 [PATCH 0/3] Support remote RPM signing Markus Lehtonen
2016-01-11 16:13 ` [PATCH 1/3] sign_rpm.bbclass: fix task dependencies Markus Lehtonen
2016-01-11 16:13 ` Markus Lehtonen [this message]
2016-01-11 16:13 ` [PATCH 3/3] oe.gpg_sign: support obs-signd Markus Lehtonen
2016-01-11 16:33 ` Mark Hatle
2016-01-12 16:24 ` Markus Lehtonen
2016-01-13 10:28 ` Markus Lehtonen
2016-01-13 14:56 ` Mark Hatle
2016-01-13 21:47 ` Mark Hatle
2016-01-21 15:28 ` Mark Hatle
[not found] ` <1453375237.13987.27.camel@linux.intel.com>
[not found] ` <56A0F794.7060603@windriver.com>
2016-01-22 10:43 ` Markus Lehtonen
2016-01-22 14:09 ` Mark Hatle
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1452528799-11292-3-git-send-email-markus.lehtonen@linux.intel.com \
--to=markus.lehtonen@linux.intel.com \
--cc=openembedded-core@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox