Skip to content

Commit 81c4ab7

Browse files
committed
Add gitlab importer
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 67dda33 commit 81c4ab7

3 files changed

Lines changed: 189 additions & 0 deletions

File tree

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ Jinja2==3.1.1
4949
jsonschema==3.2.0
5050
license-expression==21.6.14
5151
lxml==4.8.0
52+
fetchcode==0.1.0
53+
5254
Markdown==3.3.4
5355
MarkupSafe==2.1.1
5456
matplotlib-inline==0.1.3

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222
from vulnerabilities.importers import alpine_linux
2323
from vulnerabilities.importers import github
24+
from vulnerabilities.importers import gitlab
2425
from vulnerabilities.importers import nginx
2526
from vulnerabilities.importers import nvd
2627
from vulnerabilities.importers import openssl
@@ -31,6 +32,7 @@
3132
github.GitHubAPIImporter,
3233
nvd.NVDImporter,
3334
openssl.OpensslImporter,
35+
gitlab.GitLabAPIImporter,
3436
]
3537

3638
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Copyright (c) nexB Inc. and others. All rights reserved.
2+
# http://nexb.com and https://github.com/nexB/vulnerablecode/
3+
# The VulnerableCode software is licensed under the Apache License version 2.0.
4+
# Data generated with VulnerableCode require an acknowledgment.
5+
#
6+
# You may not use this software except in compliance with the License.
7+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
8+
# Unless required by applicable law or agreed to in writing, software distributed
9+
# under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR
10+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
# specific language governing permissions and limitations under the License.
12+
#
13+
# When you publish or redistribute any data created with VulnerableCode or any VulnerableCode
14+
# derivative work, you must accompany this data with the following acknowledgment:
15+
#
16+
# Generated with VulnerableCode and provided on an 'AS IS' BASIS, WITHOUT WARRANTIES
17+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
18+
# VulnerableCode should be considered or used as legal advice. Consult an Attorney
19+
# for any legal advice.
20+
# VulnerableCode is a free software from nexB Inc. and others.
21+
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
22+
23+
import logging
24+
import os
25+
from typing import Iterable
26+
from typing import List
27+
28+
import yaml
29+
from dateutil import parser as dateparser
30+
from fetchcode.vcs import fetch_via_vcs
31+
from packageurl import PackageURL
32+
from univers.version_range import RANGE_CLASS_BY_SCHEMES
33+
from univers.version_range import VersionRange
34+
35+
from vulnerabilities.importer import AdvisoryData
36+
from vulnerabilities.importer import AffectedPackage
37+
from vulnerabilities.importer import Importer
38+
from vulnerabilities.importer import Reference
39+
from vulnerabilities.importers.github import get_reference_id
40+
41+
LOGGER = logging.getLogger(__name__)
42+
43+
44+
PURL_TYPE_BY_ECOSYSTEM = {
45+
"gem": "gem",
46+
"go": "golang",
47+
"maven": "maven",
48+
"npm": "npm",
49+
"nuget": "nuget",
50+
"pypi": "pypi",
51+
"packagist": "composer",
52+
}
53+
54+
55+
def fork_and_get_dir(url):
56+
return fetch_via_vcs(url).dest_dir
57+
58+
59+
class ForkError(Exception):
60+
pass
61+
62+
63+
class GitLabAPIImporter(Importer):
64+
spdx_license_expression = "MIT"
65+
license_url = "https://gitlab.com/gitlab-org/advisories-community/-/blob/main/LICENSE"
66+
gitlab_url = "git+https://gitlab.com/gitlab-org/advisories-community/"
67+
68+
def advisory_data(self) -> Iterable[AdvisoryData]:
69+
"""
70+
Return a list of AdvisoryData objects
71+
"""
72+
try:
73+
fork_directory = fork_and_get_dir(self.gitlab_url)
74+
except Exception as e:
75+
LOGGER.error(f"Can't fetch url {self.gitlab_url}")
76+
raise ForkError(e)
77+
ecosystems = ["nuget", "maven", "gem", "npm", "go", "packagist", "pypi"]
78+
for ecosystem in ecosystems:
79+
for file in get_files(os.path.join(fork_directory, ecosystem)):
80+
yield parse_yaml_file(file)
81+
82+
83+
def get_files(dir):
84+
for root, _, files in os.walk(dir):
85+
for file in files:
86+
yield os.path.join(root, file)
87+
88+
89+
def extract_references(urls: List[str]) -> Iterable[Reference]:
90+
"""
91+
Yield `reference` by iterating over `reference_data`
92+
>>> list(extract_references(["https://github.com/advisories/GHSA-c9hw-wf7x-jp9j"]))
93+
[Reference(reference_id='GHSA-c9hw-wf7x-jp9j', url='https://github.com/advisories/GHSA-c9hw-wf7x-jp9j', severities=[])]
94+
>>> list(extract_references(["https://github.com/advisories/c9hw-wf7x-jp9j"]))
95+
[Reference(reference_id='', url='https://github.com/advisories/c9hw-wf7x-jp9j', severities=[])]
96+
"""
97+
for url in urls:
98+
if not isinstance(url, str):
99+
LOGGER.error(f"extract_references: url is not of type `str`: {url}")
100+
continue
101+
if "GHSA-" in url.upper():
102+
reference = Reference(url=url, reference_id=get_reference_id(url))
103+
else:
104+
reference = Reference(url=url)
105+
yield reference
106+
107+
108+
def not_empty(value):
109+
return value is not None and value != ""
110+
111+
112+
def get_purl(package_slug):
113+
"""
114+
Return a PackageURL object from a package slug
115+
"""
116+
LOGGER.error(package_slug)
117+
parts = package_slug.split("/")
118+
parts = list(filter(not_empty, parts))
119+
# if package slug is of the form:
120+
# "nuget/NuGet.Core"
121+
if len(parts) == 2:
122+
type, name = parts
123+
return PackageURL(type=PURL_TYPE_BY_ECOSYSTEM[type], name=name)
124+
# if package slug is of the form:
125+
# "nuget/github/user/abc/NuGet.Core"
126+
if len(parts) >= 3:
127+
type = parts[0]
128+
name = parts[-1]
129+
namespace = "/".join(parts[1:-1])
130+
return PackageURL(type=PURL_TYPE_BY_ECOSYSTEM[type], namespace=namespace, name=name)
131+
LOGGER.error(f"get_purl: package_slug can not be parsed: {package_slug!r}")
132+
133+
134+
def extract_affected_packages(
135+
affected_version_range: VersionRange, fixed_versions: List[str], purl: PackageURL
136+
) -> Iterable[AffectedPackage]:
137+
"""
138+
Yield a list of AffectedPackage objects
139+
"""
140+
for fixed_version in fixed_versions or []:
141+
yield AffectedPackage(
142+
package=purl,
143+
fixed_version=affected_version_range.version_class(fixed_version),
144+
affected_version_range=affected_version_range,
145+
)
146+
147+
148+
def parse_yaml_file(file):
149+
with open(file, "r") as f:
150+
yaml_file = yaml.safe_load(f)
151+
if not isinstance(yaml_file, dict):
152+
LOGGER.error(f"parse_yaml_file: yaml_file is not of type `dict`: {yaml_file!r}")
153+
return
154+
155+
# refer to schema here https://gitlab.com/gitlab-org/advisories-community/-/blob/main/ci/schema/schema.json
156+
aliases = yaml_file.get("identifiers")
157+
summary = yaml_file.get("title")
158+
urls = yaml_file.get("urls")
159+
references = list(extract_references(urls))
160+
date_published = dateparser.parse(yaml_file.get("pubdate"))
161+
purl: PackageURL = get_purl(yaml_file.get("package_slug"))
162+
fixed_versions = yaml_file.get("fixed_versions")
163+
vrc: VersionRange = RANGE_CLASS_BY_SCHEMES[purl.type]
164+
affected_range = yaml_file.get("affected_range")
165+
affected_version_range = None
166+
try:
167+
affected_version_range = vrc.from_native(affected_range) if affected_range else None
168+
except Exception:
169+
LOGGER.error(f"parse_yaml_file: affected_range is not parsable`: {affected_range!r}")
170+
171+
if affected_version_range == NotImplementedError:
172+
LOGGER.error(f"parse_yaml_file: from_native is not implemented yet for {vrc.__name__}`")
173+
affected_version_range = None
174+
175+
affected_packages = list(
176+
extract_affected_packages(affected_version_range, fixed_versions, purl)
177+
)
178+
179+
return AdvisoryData(
180+
aliases=aliases,
181+
summary=summary,
182+
references=references,
183+
date_published=date_published,
184+
affected_packages=affected_packages,
185+
)

0 commit comments

Comments
 (0)