Add a new sync-git-verify-commit-signature option (defaulting to false) that verifies the top commit signature after syncing. The verification is currently done using built-in git routines.
The verification passes if the signature is good or untrusted. In the latter case, a warning is printed. In any other case, the verification causes sync to fail and an appropriate error is output. --- man/portage.5 | 4 +++ pym/portage/sync/modules/git/__init__.py | 3 +- pym/portage/sync/modules/git/git.py | 48 ++++++++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/man/portage.5 b/man/portage.5 index 778dedfd5..2d5091109 100644 --- a/man/portage.5 +++ b/man/portage.5 @@ -1007,6 +1007,10 @@ See also example for sync-git-clone-env. .B sync\-git\-pull\-extra\-opts Extra options to give to git when updating repository (git pull). .TP +.B sync\-git\-verify\-commit\-signature = true|false +Require the top commit in the repository to contain a good OpenPGP +signature. Defaults to false. +.TP .B sync\-hooks\-only\-on\-change If set to true, then sync of a given repository will not trigger postsync hooks unless hooks would have executed for a master repository or the diff --git a/pym/portage/sync/modules/git/__init__.py b/pym/portage/sync/modules/git/__init__.py index 2f1d35226..270d97186 100644 --- a/pym/portage/sync/modules/git/__init__.py +++ b/pym/portage/sync/modules/git/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2014-2017 Gentoo Foundation +# Copyright 2014-2018 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 doc = """Git plug-in module for portage. @@ -58,6 +58,7 @@ module_spec = { 'sync-git-env', 'sync-git-pull-env', 'sync-git-pull-extra-opts', + 'sync-git-verify-commit-signature', ), } } diff --git a/pym/portage/sync/modules/git/git.py b/pym/portage/sync/modules/git/git.py index 8b4cab273..7e5ddf3b5 100644 --- a/pym/portage/sync/modules/git/git.py +++ b/pym/portage/sync/modules/git/git.py @@ -1,4 +1,4 @@ -# Copyright 2005-2017 Gentoo Foundation +# Copyright 2005-2018 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import logging @@ -7,7 +7,7 @@ import subprocess import portage from portage import os from portage.util import writemsg_level, shlex_split -from portage.output import create_color_func +from portage.output import create_color_func, EOutput good = create_color_func("GOOD") bad = create_color_func("BAD") warn = create_color_func("WARN") @@ -71,6 +71,7 @@ class GitSync(NewBase): else: # default git_cmd_opts += " --depth 1" + if self.repo.module_specific_options.get('sync-git-clone-extra-opts'): git_cmd_opts += " %s" % self.repo.module_specific_options['sync-git-clone-extra-opts'] git_cmd = "%s clone%s %s ." % (self.bin_command, git_cmd_opts, @@ -85,6 +86,8 @@ class GitSync(NewBase): self.logger(self.xterm_titles, msg) writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1) return (exitcode, False) + if not self.verify_head(): + return (1, False) return (os.EX_OK, True) @@ -125,12 +128,53 @@ class GitSync(NewBase): self.logger(self.xterm_titles, msg) writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1) return (exitcode, False) + if not self.verify_head(): + return (1, False) current_rev = subprocess.check_output(rev_cmd, cwd=portage._unicode_encode(self.repo.location)) return (os.EX_OK, current_rev != previous_rev) + def verify_head(self): + if (self.repo.module_specific_options.get( + 'sync-git-verify-commit-signature', 'false') != 'true'): + return True + + rev_cmd = [self.bin_command, "log", "--pretty=format:%G?", "-1"] + try: + status = (portage._unicode_decode( + subprocess.check_output(rev_cmd, + cwd=portage._unicode_encode(self.repo.location))) + .strip()) + except subprocess.CalledProcessError: + return False + + out = EOutput() + if status == 'G': # good signature is good + out.einfo('Trusted signature found on top commit') + return True + elif status == 'U': # untrusted + out.ewarn('Top commit signature is valid but not trusted') + return True + else: + if status == 'B': + expl = 'bad signature' + elif status == 'X': + expl = 'expired signature' + elif status == 'Y': + expl = 'expired key' + elif status == 'R': + expl = 'revoked key' + elif status == 'E': + expl = 'unable to verify signature (missing key?)' + elif status == 'N': + expl = 'no signature' + else: + expl = 'unknown issue' + out.eerror('No valid signature found: %s' % (expl,)) + return False + def retrieve_head(self, **kwargs): '''Get information about the head commit''' if kwargs: -- 2.16.1