Gentoo Archives: gentoo-portage-dev

From: "Michał Górny" <mgorny@g.o>
To: gentoo-portage-dev@l.g.o
Cc: "Michał Górny" <mgorny@g.o>
Subject: [gentoo-portage-dev] [PATCH 1/2] git: Support verifying commit signature post-sync
Date: Thu, 01 Feb 2018 13:31:33
Message-Id: 20180201133114.27437-1-mgorny@gentoo.org
1 Add a new sync-git-verify-commit-signature option (defaulting to false)
2 that verifies the top commit signature after syncing. The verification
3 is currently done using built-in git routines.
4
5 The verification passes if the signature is good or untrusted.
6 In the latter case, a warning is printed. In any other case,
7 the verification causes sync to fail and an appropriate error is output.
8 ---
9 man/portage.5 | 4 +++
10 pym/portage/sync/modules/git/__init__.py | 3 +-
11 pym/portage/sync/modules/git/git.py | 48 ++++++++++++++++++++++++++++++--
12 3 files changed, 52 insertions(+), 3 deletions(-)
13
14 diff --git a/man/portage.5 b/man/portage.5
15 index d4f755f51..da5a02f5a 100644
16 --- a/man/portage.5
17 +++ b/man/portage.5
18 @@ -1007,6 +1007,10 @@ See also example for sync-git-clone-env.
19 .B sync\-git\-pull\-extra\-opts
20 Extra options to give to git when updating repository (git pull).
21 .TP
22 +.B sync\-git\-verify\-commit\-signature = true|false
23 +Require the top commit in the repository to contain a good OpenPGP
24 +signature. Defaults to false.
25 +.TP
26 .B sync\-hooks\-only\-on\-change
27 If set to true, then sync of a given repository will not trigger postsync
28 hooks unless hooks would have executed for a master repository or the
29 diff --git a/pym/portage/sync/modules/git/__init__.py b/pym/portage/sync/modules/git/__init__.py
30 index 2f1d35226..270d97186 100644
31 --- a/pym/portage/sync/modules/git/__init__.py
32 +++ b/pym/portage/sync/modules/git/__init__.py
33 @@ -1,4 +1,4 @@
34 -# Copyright 2014-2017 Gentoo Foundation
35 +# Copyright 2014-2018 Gentoo Foundation
36 # Distributed under the terms of the GNU General Public License v2
37
38 doc = """Git plug-in module for portage.
39 @@ -58,6 +58,7 @@ module_spec = {
40 'sync-git-env',
41 'sync-git-pull-env',
42 'sync-git-pull-extra-opts',
43 + 'sync-git-verify-commit-signature',
44 ),
45 }
46 }
47 diff --git a/pym/portage/sync/modules/git/git.py b/pym/portage/sync/modules/git/git.py
48 index 8b4cab273..7e5ddf3b5 100644
49 --- a/pym/portage/sync/modules/git/git.py
50 +++ b/pym/portage/sync/modules/git/git.py
51 @@ -1,4 +1,4 @@
52 -# Copyright 2005-2017 Gentoo Foundation
53 +# Copyright 2005-2018 Gentoo Foundation
54 # Distributed under the terms of the GNU General Public License v2
55
56 import logging
57 @@ -7,7 +7,7 @@ import subprocess
58 import portage
59 from portage import os
60 from portage.util import writemsg_level, shlex_split
61 -from portage.output import create_color_func
62 +from portage.output import create_color_func, EOutput
63 good = create_color_func("GOOD")
64 bad = create_color_func("BAD")
65 warn = create_color_func("WARN")
66 @@ -71,6 +71,7 @@ class GitSync(NewBase):
67 else:
68 # default
69 git_cmd_opts += " --depth 1"
70 +
71 if self.repo.module_specific_options.get('sync-git-clone-extra-opts'):
72 git_cmd_opts += " %s" % self.repo.module_specific_options['sync-git-clone-extra-opts']
73 git_cmd = "%s clone%s %s ." % (self.bin_command, git_cmd_opts,
74 @@ -85,6 +86,8 @@ class GitSync(NewBase):
75 self.logger(self.xterm_titles, msg)
76 writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
77 return (exitcode, False)
78 + if not self.verify_head():
79 + return (1, False)
80 return (os.EX_OK, True)
81
82
83 @@ -125,12 +128,53 @@ class GitSync(NewBase):
84 self.logger(self.xterm_titles, msg)
85 writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
86 return (exitcode, False)
87 + if not self.verify_head():
88 + return (1, False)
89
90 current_rev = subprocess.check_output(rev_cmd,
91 cwd=portage._unicode_encode(self.repo.location))
92
93 return (os.EX_OK, current_rev != previous_rev)
94
95 + def verify_head(self):
96 + if (self.repo.module_specific_options.get(
97 + 'sync-git-verify-commit-signature', 'false') != 'true'):
98 + return True
99 +
100 + rev_cmd = [self.bin_command, "log", "--pretty=format:%G?", "-1"]
101 + try:
102 + status = (portage._unicode_decode(
103 + subprocess.check_output(rev_cmd,
104 + cwd=portage._unicode_encode(self.repo.location)))
105 + .strip())
106 + except subprocess.CalledProcessError:
107 + return False
108 +
109 + out = EOutput()
110 + if status == 'G': # good signature is good
111 + out.einfo('Trusted signature found on top commit')
112 + return True
113 + elif status == 'U': # untrusted
114 + out.ewarn('Top commit signature is valid but not trusted')
115 + return True
116 + else:
117 + if status == 'B':
118 + expl = 'bad signature'
119 + elif status == 'X':
120 + expl = 'expired signature'
121 + elif status == 'Y':
122 + expl = 'expired key'
123 + elif status == 'R':
124 + expl = 'revoked key'
125 + elif status == 'E':
126 + expl = 'unable to verify signature (missing key?)'
127 + elif status == 'N':
128 + expl = 'no signature'
129 + else:
130 + expl = 'unknown issue'
131 + out.eerror('No valid signature found: %s' % (expl,))
132 + return False
133 +
134 def retrieve_head(self, **kwargs):
135 '''Get information about the head commit'''
136 if kwargs:
137 --
138 2.16.1

Replies