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 |
+ ''') |