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 |