Merge ~litios/ubuntu-cve-tracker:customer_ppa_metadata into ubuntu-cve-tracker:master

Proposed by David Fernandez Gonzalez
Status: Rejected
Rejected by: David Fernandez Gonzalez
Proposed branch: ~litios/ubuntu-cve-tracker:customer_ppa_metadata
Merge into: ubuntu-cve-tracker:master
Diff against target: 228 lines (+102/-42)
8 files modified
scripts/cve_lib.py (+32/-42)
scripts/test_cve_lib.py (+36/-0)
test/subprojects/bar/focal/supported.txt (+1/-0)
test/subprojects/bar/priority-critical (+8/-0)
test/subprojects/bar/priority-invalid-value (+8/-0)
test/subprojects/foo/focal/supported.txt (+1/-0)
test/subprojects/foo/priority-critical (+8/-0)
test/subprojects/foo/priority-invalid-value (+8/-0)
Reviewer Review Type Date Requested Status
David Fernandez Gonzalez Disapprove
Emilia Torino Pending
Eduardo Barretto Pending
Review via email: mp+442260@code.launchpad.net

Commit message

Currently, only UCT CVEs could contain metadata fields. This commit
allows other project CVEs to have metadata, that will be loaded
into the main structure under the External-Metadata tag

Description of the change

The following fields have been considered for the external CVEs:

> 'Assigned-to', 'Notes', 'Priority', 'References'

The main motivation behind this change is so we can assign a different person to a subproject CVE as well as priority, as that may differ from the one designed in the Ubuntu CVE.

If metadata is present on the subproject CVE, it will be picked up when loading the main CVE file. If not, it will simply omit it.

Example: https://pastebin.canonical.com/p/dXnxqm3Yyg/
Example with error: https://pastebin.canonical.com/p/66S2Mp7JYJ/

To post a comment you must log in.
Revision history for this message
Eduardo Barretto (ebarretto) wrote :

hey, thanks for this changes, before checking it, could you please run and paste the output:
$ python3-coverage run -a -m pytest scripts/

I also wonder if we should move those tests hidden inside $UCT/scripts to $UCT/test. But this could be done in a separate PR.

Revision history for this message
Alex Murray (alexmurray) wrote :

I am a bit concerned that introducing a new field 'External-Metadata' may be a bit unmaintainable as we have to remember to always check this and process it separately.

I wonder if instead it would be better to introduce the concept of making these element first class, and so making the top-level fields of Assigned-to, Notes, Priority and References always be dictionaries, keyed on the package name - this would mean a lot more refactoring but I wonder if it may be better in the long-run?

Note this is just an idea, so feel free to ignore but I would be interested to know others thoughts on it.

Revision history for this message
David Fernandez Gonzalez (litios) wrote :

To give a little bit of reasoning: my idea was to provide a new field to make this whole extra metadata completely optional and also because of potential new fields that could appear for special projects.

To treat that information as an appendix that may or may not exist and that we would use in customer-specific tooling instead of UCT.

But I like the idea of moving those fields to dictionaries. That would make the structure look cleaner. I was worried about the repercussions of those changes, as some of those fields are published and we would need to do some filtering over those fields.

One thing that I didn't understand is the keyed by package name. As of today, we are "bundling" those fields by the project (i.e. Ubuntu CVE and subproject CVE). Are you proposing to have those split by package? I was thinking about it as https://pastebin.canonical.com/p/M9QBWSwQpx/

I would be happy to come up with a proposal for the dictionary idea though.

Revision history for this message
Alex Murray (alexmurray) wrote :

Ah that seems like a better idea to have them keyed by projects rather than packages - since you are all in the room together in Prague, perhaps see what others think and if you can get some consensus, then go ahead with a more formal proposal?

Revision history for this message
David Fernandez Gonzalez (litios) wrote :

Sure, will do! We will try to talk about it in the "All the tech debt" meeting.
Thanks for taking the time to review it

Revision history for this message
David Fernandez Gonzalez (litios) wrote :

As discussed during the meeting, AlexM's idea with subprojects keys seems like the best approach.
We may want to also extend this for the pkgs, as right now everything is on the same level.

review: Disapprove

Unmerged commits

5ba83c0... by David Fernandez Gonzalez

cve_lib: add tests for external project metadata CVEs

Failed
[SUCCEEDED] unit-tests:0 (build)
[FAILED] check-cves:0 (build)
12 of 2 results
476f5b9... by David Fernandez Gonzalez

cve_lib: Allow metadata fields on external project CVEs

Currently, only UCT CVEs could contain metadata fields. This commit
allows other project CVEs to have metadata, that will be loaded
into the main structure under the External-Metadata tag

Failed
[SUCCEEDED] unit-tests:0 (build)
[FAILED] check-cves:0 (build)
12 of 2 results

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/scripts/cve_lib.py b/scripts/cve_lib.py
2index d116f48..afd7749 100755
3--- a/scripts/cve_lib.py
4+++ b/scripts/cve_lib.py
5@@ -1993,47 +1993,35 @@ class NotesParser(object):
6
7
8
9-def amend_external_subproject_pkg(cve, data, srcmap, amendments, code, msg):
10- linenum = 0
11- for line in amendments.splitlines():
12- linenum += 1
13- if len(line) == 0 or line.startswith('#') or line.startswith(' '):
14- continue
15- try:
16- field, value = line.split(':', 1)
17- field = field.strip()
18- value = value.strip()
19- except ValueError as e:
20- msg += "%s: bad line '%s' (%s)\n" % (cve, line, e)
21- code = EXIT_FAIL
22- return code, msg
23-
24- if '_' in field:
25- success, pkg, release, state, details, code, msg = parse_cve_release_package_field(cve, field, data, value, code, msg, linenum)
26- if not success:
27- return code, msg
28-
29- data.setdefault("pkgs", dict())
30- data["pkgs"].setdefault(pkg, dict())
31- srcmap["pkgs"].setdefault(pkg, dict())
32- # override existing release info if it exists
33- data["pkgs"][pkg][release] = [state, details]
34- srcmap["pkgs"][pkg][release] = (cve, linenum)
35-
36- return code, msg
37-
38-
39 def load_external_subproject_cve_data(cve, data, srcmap, code, msg):
40 cve_id = os.path.basename(cve)
41+ extra_fields = ['Assigned-to', 'Notes', 'Priority', 'References']
42 for f in find_external_subproject_cves(cve_id):
43- with codecs.open(f, 'r', encoding="utf-8") as fp:
44- amendments = fp.read()
45- fp.close()
46- code, msg = amend_external_subproject_pkg(f, data, srcmap, amendments, code, msg)
47+ subproject = f.rsplit('/', 2)[1]
48+ try:
49+ external_data = load_cve(f, srcmap=srcmap, is_external=True)
50+ for pkg in external_data['pkgs']:
51+ for rel in external_data['pkgs'][pkg]:
52+ state, details = external_data['pkgs'][pkg][rel]
53+ data.setdefault("pkgs", dict())
54+ data["pkgs"].setdefault(pkg, dict())
55+ # override existing release info if it exists
56+ data["pkgs"][pkg][rel] = [state, details]
57+
58+ for field in extra_fields:
59+ if field in external_data and \
60+ external_data[field] != [] and external_data[field] != '' and \
61+ external_data[field] != 'untriaged':
62+ data.setdefault("External-Metadata", dict())
63+ data["External-Metadata"].setdefault(subproject, dict())
64+ data["External-Metadata"][subproject][field] = external_data[field]
65+
66+ except ValueError as e:
67+ return EXIT_FAIL, msg + str(e)
68
69 return code, msg
70
71-def load_cve(cve, strict=False, srcmap=None):
72+def load_cve(cve, strict=False, srcmap=None, is_external=False):
73 '''Loads a given CVE into:
74 dict( fields...
75 'pkgs' -> dict( pkg -> dict( release -> (state, details) ) )
76@@ -2225,12 +2213,13 @@ def load_cve(cve, strict=False, srcmap=None):
77 if strict:
78 nonempty += ['PublicDate']
79
80- if field not in data or field not in fields_seen:
81- msg += "%s: %d: missing field '%s'\n" % (cve, linenum, field)
82- code = EXIT_FAIL
83- elif field in nonempty and data[field].strip() == "":
84- msg += "%s: %d: required field '%s' is empty\n" % (cve, linenum, field)
85- code = EXIT_FAIL
86+ if not is_external:
87+ if field not in data or field not in fields_seen:
88+ msg += "%s: %d: missing field '%s'\n" % (cve, linenum, field)
89+ code = EXIT_FAIL
90+ elif field in nonempty and data[field].strip() == "":
91+ msg += "%s: %d: required field '%s' is empty\n" % (cve, linenum, field)
92+ code = EXIT_FAIL
93
94 # Fill in defaults for missing fields
95 if 'Priority' not in data:
96@@ -2259,7 +2248,8 @@ def load_cve(cve, strict=False, srcmap=None):
97
98 data['pkgs'] = affected
99
100- code, msg = load_external_subproject_cve_data(cve, data, srcmap, code, msg)
101+ if not is_external:
102+ code, msg = load_external_subproject_cve_data(cve, data, srcmap, code, msg)
103
104 if code != EXIT_OKAY:
105 raise ValueError(msg.strip())
106diff --git a/scripts/test_cve_lib.py b/scripts/test_cve_lib.py
107index f9bd1d2..0f1d78e 100755
108--- a/scripts/test_cve_lib.py
109+++ b/scripts/test_cve_lib.py
110@@ -186,6 +186,10 @@ class TestParseCVEFiles:
111 assert cvss[0]['baseSeverity'] == js['baseMetricV3']['cvssV3']['baseSeverity']
112
113
114+MOCK_SUBPROJECTS = [
115+ "foo","bar"
116+]
117+
118 class TestSubprojects:
119
120 def test_load_subprojects_loads_every_config_available(self):
121@@ -204,4 +208,36 @@ class TestSubprojects:
122 if project and "customer" in project:
123 assert cve_lib.subprojects[rel]["customer"] == project["customer"]
124
125+ @pytest.mark.parametrize("subproject", MOCK_SUBPROJECTS)
126+ def test_external_subproject_metadata(self, subproject):
127+ real_subproject_dir = cve_lib.subprojects_dir
128+ cve_lib.subprojects_dir = os.path.join(TEST_DATA_DIR, 'subprojects')
129+ cve_lib.load_external_subprojects()
130+
131+ cve = cve_lib.load_cve(os.path.join(TEST_DATA_DIR, 'okay/priority-critical'), strict=True)
132+ assert cve['External-Metadata']
133+ assert cve['External-Metadata'][subproject]
134+
135+ subproject_cve_data = cve_lib.load_cve(os.path.join(TEST_DATA_DIR, 'subprojects', subproject, 'priority-critical'), is_external=True)
136+
137+ for key in cve['External-Metadata'][subproject]:
138+ assert cve['External-Metadata'][subproject][key] == subproject_cve_data[key]
139+
140+ cve_lib.subprojects_dir = real_subproject_dir
141+ cve_lib.load_external_subprojects()
142+
143+ @pytest.mark.parametrize("subproject", MOCK_SUBPROJECTS)
144+ def test_invalid_external_subproject_metadata(self, subproject):
145+ real_subproject_dir = cve_lib.subprojects_dir
146+ cve_lib.subprojects_dir = os.path.join(TEST_DATA_DIR, 'subprojects')
147+ cve_lib.load_external_subprojects()
148+ cve_lib.external_releases = [os.path.join(subproject, 'focal')]
149+
150+ error = f"test/bad/priority-invalid-value: 13: unknown Priority 'meduim'\n"
151+ subproject_err = f"test/subprojects/{subproject}/priority-invalid-value: 5: unknown Priority"
152+
153+ with pytest.raises(ValueError, match=error+subproject_err):
154+ cve_lib.load_cve(os.path.join(TEST_DATA_DIR, 'bad/priority-invalid-value'), strict=True)
155
156+ cve_lib.subprojects_dir = real_subproject_dir
157+ cve_lib.load_external_subprojects()
158\ No newline at end of file
159diff --git a/test/subprojects/bar/focal/supported.txt b/test/subprojects/bar/focal/supported.txt
160new file mode 100644
161index 0000000..83b3780
162--- /dev/null
163+++ b/test/subprojects/bar/focal/supported.txt
164@@ -0,0 +1 @@
165+ppp
166diff --git a/test/subprojects/bar/priority-critical b/test/subprojects/bar/priority-critical
167new file mode 100644
168index 0000000..4b7736d
169--- /dev/null
170+++ b/test/subprojects/bar/priority-critical
171@@ -0,0 +1,8 @@
172+References:
173+ https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0001
174+Notes:
175+ bar> Test
176+Priority: critical
177+Assigned-to: bar
178+
179+bar/focal_ppp: released (2.4.7-2+4.1ubuntu8)
180diff --git a/test/subprojects/bar/priority-invalid-value b/test/subprojects/bar/priority-invalid-value
181new file mode 100644
182index 0000000..c826aa7
183--- /dev/null
184+++ b/test/subprojects/bar/priority-invalid-value
185@@ -0,0 +1,8 @@
186+References:
187+ https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0002
188+Notes:
189+ foo> Test
190+Priority: Critical
191+Assigned-to: foo
192+
193+bar/focal_ppp: released (2.4.7-2+4.1ubuntu8)
194diff --git a/test/subprojects/foo/focal/supported.txt b/test/subprojects/foo/focal/supported.txt
195new file mode 100644
196index 0000000..83b3780
197--- /dev/null
198+++ b/test/subprojects/foo/focal/supported.txt
199@@ -0,0 +1 @@
200+ppp
201diff --git a/test/subprojects/foo/priority-critical b/test/subprojects/foo/priority-critical
202new file mode 100644
203index 0000000..4035ba8
204--- /dev/null
205+++ b/test/subprojects/foo/priority-critical
206@@ -0,0 +1,8 @@
207+References:
208+ https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0002
209+Notes:
210+ foo> Test
211+Priority: low
212+Assigned-to: foo
213+
214+foo/focal_ppp: released (2.4.7-2+4.1ubuntu8)
215diff --git a/test/subprojects/foo/priority-invalid-value b/test/subprojects/foo/priority-invalid-value
216new file mode 100644
217index 0000000..6ac754d
218--- /dev/null
219+++ b/test/subprojects/foo/priority-invalid-value
220@@ -0,0 +1,8 @@
221+References:
222+ https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0002
223+Notes:
224+ foo> Test
225+Priority: Low
226+Assigned-to: foo
227+
228+foo/focal_ppp: released (2.4.7-2+4.1ubuntu8)

Subscribers

People subscribed via source and target branches