Gentoo Archives: gentoo-commits

From: Arthur Zamarin <arthurzam@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/pkgcore/pkgcheck:master commit in: tests/checks/, src/pkgcheck/checks/
Date: Mon, 31 Oct 2022 18:53:53
Message-Id: 1667240995.4e69c9d901c37d631cfff6316103e4be53b214c3.arthurzam@gentoo
1 commit: 4e69c9d901c37d631cfff6316103e4be53b214c3
2 Author: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
3 AuthorDate: Sun Oct 30 18:36:27 2022 +0000
4 Commit: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
5 CommitDate: Mon Oct 31 18:29:55 2022 +0000
6 URL: https://gitweb.gentoo.org/proj/pkgcore/pkgcheck.git/commit/?id=4e69c9d9
7
8 AcctCheck: use metadata/qa-policy.conf for dynamic id range
9
10 Load UID and GID range from metadata/qa-policy.conf under the repo,
11 validate the format is as expected, and use the values to set expected
12 ids.
13
14 Resolves: https://github.com/pkgcore/pkgcheck/issues/356
15 Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>
16
17 src/pkgcheck/checks/acct.py | 60 +++++++++++++++++++++++++++++----------
18 tests/checks/test_acct.py | 69 ++++++++++++++++++++++++++++++++++++++++-----
19 2 files changed, 107 insertions(+), 22 deletions(-)
20
21 diff --git a/src/pkgcheck/checks/acct.py b/src/pkgcheck/checks/acct.py
22 index cb0053e3..4f144023 100644
23 --- a/src/pkgcheck/checks/acct.py
24 +++ b/src/pkgcheck/checks/acct.py
25 @@ -2,14 +2,16 @@
26
27 import re
28 from collections import defaultdict
29 +from configparser import ConfigParser
30 from functools import partial
31 from itertools import chain
32
33 from pkgcore.ebuild import restricts
34 from pkgcore.restrictions import packages
35 +from snakeoil.osutils import pjoin
36
37 from .. import results, sources
38 -from . import GentooRepoCheck, RepoCheck
39 +from . import GentooRepoCheck, RepoCheck, SkipCheck
40
41
42 class MissingAccountIdentifier(results.VersionResult, results.Warning):
43 @@ -35,13 +37,16 @@ class ConflictingAccountIdentifiers(results.Error):
44
45 @property
46 def desc(self):
47 - return (
48 - f"conflicting {self.kind} id {self.identifier} usage: "
49 - f"[ {', '.join(self.pkgs)} ]")
50 + pkgs = ', '.join(self.pkgs)
51 + return f"conflicting {self.kind} id {self.identifier} usage: [ {pkgs} ]"
52
53
54 class OutsideRangeAccountIdentifier(results.VersionResult, results.Error):
55 - """UID/GID outside allowed allocation range."""
56 + """UID/GID outside allowed allocation range.
57 +
58 + To view the range accepted for this repository, look at the file
59 + ``metadata/qa-policy.conf`` in the section ``user-group-ids``.
60 + """
61
62 def __init__(self, kind, identifier, **kwargs):
63 super().__init__(**kwargs)
64 @@ -50,16 +55,20 @@ class OutsideRangeAccountIdentifier(results.VersionResult, results.Error):
65
66 @property
67 def desc(self):
68 - return (
69 - f"{self.kind} id {self.identifier} outside permitted "
70 - f"static allocation range (0..499, 60001+)")
71 + return (f"{self.kind} id {self.identifier} outside permitted "
72 + f"static allocation range")
73
74
75 class AcctCheck(GentooRepoCheck, RepoCheck):
76 """Various checks for acct-* packages.
77
78 Verify that acct-* packages do not use conflicting, invalid or out-of-range
79 - UIDs/GIDs.
80 + UIDs/GIDs. This check uses a special file ``metadata/qa-policy.conf``
81 + located within the repository. It should contain a ``user-group-ids``
82 + section containing two keys: ``uid-range`` and ``gid-range``, which consist
83 + of a comma separated list, either ``<n>`` for a single value or ``<m>-<n>``
84 + for a range of values (including both ends). In case this file doesn't
85 + exist or is wrongly defined, this check is skipped.
86 """
87
88 _restricted_source = (sources.RestrictionRepoSource, (packages.OrRestriction(*(
89 @@ -76,14 +85,37 @@ class AcctCheck(GentooRepoCheck, RepoCheck):
90 r'ACCT_(?P<var>USER|GROUP)_ID=(?P<quot>[\'"]?)(?P<id>[0-9]+)(?P=quot)')
91 self.seen_uids = defaultdict(partial(defaultdict, list))
92 self.seen_gids = defaultdict(partial(defaultdict, list))
93 + uid_range, gid_range = self.load_ids_from_configuration(self.options.target_repo)
94 self.category_map = {
95 - 'acct-user': (self.seen_uids, 'USER', (65534,)),
96 - 'acct-group': (self.seen_gids, 'GROUP', (65533, 65534)),
97 + 'acct-user': (self.seen_uids, 'USER', tuple(uid_range)),
98 + 'acct-group': (self.seen_gids, 'GROUP', tuple(gid_range)),
99 }
100
101 + def parse_config_id_range(self, config: ConfigParser, config_key: str):
102 + id_ranges = config['user-group-ids'].get(config_key, None)
103 + if not id_ranges:
104 + raise SkipCheck(self, f"metadata/qa-policy.conf: missing value for {config_key}")
105 + try:
106 + for id_range in map(str.strip, id_ranges.split(',')):
107 + start, *end = map(int, id_range.split('-', maxsplit=1))
108 + if len(end) == 0:
109 + yield range(start, start + 1)
110 + else:
111 + yield range(start, end[0] + 1)
112 + except ValueError:
113 + raise SkipCheck(self, f"metadata/qa-policy.conf: invalid value for {config_key}")
114 +
115 + def load_ids_from_configuration(self, repo):
116 + config = ConfigParser()
117 + if not config.read(pjoin(repo.location, 'metadata', 'qa-policy.conf')):
118 + raise SkipCheck(self, "failed loading 'metadata/qa-policy.conf'")
119 + if 'user-group-ids' not in config:
120 + raise SkipCheck(self, "metadata/qa-policy.conf: missing section user-group-ids")
121 + return self.parse_config_id_range(config, 'uid-range'), self.parse_config_id_range(config, 'gid-range')
122 +
123 def feed(self, pkg):
124 try:
125 - seen_id_map, expected_var, extra_allowed_ids = self.category_map[pkg.category]
126 + seen_id_map, expected_var, allowed_ids = self.category_map[pkg.category]
127 except KeyError:
128 return
129
130 @@ -96,9 +128,7 @@ class AcctCheck(GentooRepoCheck, RepoCheck):
131 yield MissingAccountIdentifier(f"ACCT_{expected_var}_ID", pkg=pkg)
132 return
133
134 - # all UIDs/GIDs must be in <750, with special exception
135 - # of nobody/nogroup which use 65534/65533
136 - if found_id >= 750 and found_id not in extra_allowed_ids:
137 + if not any(found_id in id_range for id_range in allowed_ids):
138 yield OutsideRangeAccountIdentifier(expected_var.lower(), found_id, pkg=pkg)
139 return
140
141
142 diff --git a/tests/checks/test_acct.py b/tests/checks/test_acct.py
143 index cd8f852f..57273705 100644
144 --- a/tests/checks/test_acct.py
145 +++ b/tests/checks/test_acct.py
146 @@ -1,4 +1,7 @@
147 -from pkgcheck.checks import acct
148 +import textwrap
149 +
150 +import pytest
151 +from pkgcheck.checks import acct, SkipCheck
152 from pkgcore.test.misc import FakeRepo
153 from snakeoil.cli import arghparse
154
155 @@ -11,17 +14,27 @@ class TestAcctUser(misc.ReportTestCase):
156
157 kind = 'user'
158
159 + @pytest.fixture(autouse=True)
160 + def _setup(self, tmp_path):
161 + (metadata := tmp_path / 'metadata').mkdir()
162 + (metadata / 'qa-policy.conf').write_text(textwrap.dedent("""\
163 + [user-group-ids]
164 + uid-range = 0-749,65534
165 + gid-range = 0-749,65533,65534
166 + """))
167 + self.location = str(tmp_path)
168 +
169 def mk_check(self, pkgs):
170 - self.repo = FakeRepo(pkgs=pkgs, repo_id='test')
171 - check = self.check_kls(arghparse.Namespace(target_repo=self.repo, gentoo_repo=True))
172 + repo = FakeRepo(pkgs=pkgs, repo_id='test', location=self.location)
173 + check = self.check_kls(arghparse.Namespace(target_repo=repo, gentoo_repo=True))
174 return check
175
176 def mk_pkg(self, name, identifier, version=1, ebuild=None):
177 if ebuild is None:
178 - ebuild = f'''
179 -inherit acct-{self.kind}
180 -ACCT_{self.kind.upper()}_ID="{identifier}"
181 -'''
182 + ebuild = textwrap.dedent(f'''\
183 + inherit acct-{self.kind}
184 + ACCT_{self.kind.upper()}_ID="{identifier}"
185 + ''')
186 return misc.FakePkg(f'acct-{self.kind}/{name}-{version}', ebuild=ebuild)
187
188 def test_unmatching_pkgs(self):
189 @@ -33,6 +46,7 @@ ACCT_{self.kind.upper()}_ID="{identifier}"
190 def test_correct_ids(self):
191 pkgs = (self.mk_pkg('foo', 100),
192 self.mk_pkg('bar', 200),
193 + self.mk_pkg('test', 749),
194 self.mk_pkg('nobody', 65534))
195 check = self.mk_check(pkgs)
196 self.assertNoReport(check, pkgs)
197 @@ -110,3 +124,44 @@ class TestAcctGroup(TestAcctUser):
198 pkg = self.mk_pkg('nogroup', 65533)
199 check = self.mk_check((pkg,))
200 self.assertNoReport(check, pkg)
201 +
202 +
203 +class TestQaPolicyValidation(misc.ReportTestCase):
204 +
205 + def mk_check(self, tmp_path, content):
206 + if content:
207 + (metadata := tmp_path / 'metadata').mkdir()
208 + (metadata / 'qa-policy.conf').write_text(textwrap.dedent(content))
209 + repo = FakeRepo(repo_id='test', location=str(tmp_path))
210 + return acct.AcctCheck(arghparse.Namespace(target_repo=repo, gentoo_repo=True))
211 +
212 + def test_missing_qa_policy(self, tmp_path):
213 + with pytest.raises(SkipCheck, match="failed loading 'metadata/qa-policy.conf'"):
214 + self.mk_check(tmp_path, None)
215 +
216 + def test_missing_section(self, tmp_path):
217 + with pytest.raises(SkipCheck, match="missing section user-group-ids"):
218 + self.mk_check(tmp_path, '''\
219 + [random]
220 + x = 5
221 + ''')
222 +
223 + def test_missing_config(self, tmp_path):
224 + with pytest.raises(SkipCheck, match="missing value for gid-range"):
225 + self.mk_check(tmp_path, '''\
226 + [user-group-ids]
227 + uid-range = 0-749
228 + ''')
229 +
230 + @pytest.mark.parametrize('value', (
231 + 'start-end',
232 + '0-749-1500',
233 + ',150',
234 + ))
235 + def test_invalid_value(self, tmp_path, value):
236 + with pytest.raises(SkipCheck, match="invalid value for uid-range"):
237 + self.mk_check(tmp_path, f'''\
238 + [user-group-ids]
239 + uid-range = {value}
240 + gid-range = 0-749
241 + ''')