Skip to content

Commit 0b87b0c

Browse files
author
Nisha K
authored
Collect dpkg and rpm source pkg info
This PR adds source package name and source package version information to the Package object data model. It also adds scripts in base.yml for rpm and dpkg package managers to collect source package names and versions. Tern currently reports binary package metadata in its reports. Source packages exist in operating systems like Debian and RedHat and differ from binary packages. Source packages provide all of the necessary files to compile or build a desired piece of software. Binary packages are what get produced as a result of building a source package and are what typically gets installed in an environment. Binary packages can have different names and/or versions as their source package. Source packages are relevant in the context of security scanning as most CVEs are reported by source package name and version. Resolves #1083 Signed-off-by: Rose Judge <[email protected]>
2 parents 5011db0 + 94aacb1 commit 0b87b0c

File tree

5 files changed

+69
-6
lines changed

5 files changed

+69
-6
lines changed

tern/analyze/default/bundle.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ def convert_to_pkg_dicts(attr_lists):
5050
'copyright': 'copyrights',
5151
'proj_url': 'proj_urls',
5252
'pkg_licenses': 'pkg_licenses',
53-
'files': 'files'}
53+
'files': 'files',
54+
'src_name': 'source_names',
55+
'src_version': 'source_versions'}
5456
pkg_list = []
5557
len_names = len(attr_lists['names'])
5658
# make a list of keys that correspond with package property names

tern/analyze/default/command_lib/base.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ dpkg:
8888
container:
8989
- "dpkg-query -W -f '${Version}\n'"
9090
delimiter: "\n"
91+
source_names:
92+
invoke:
93+
1:
94+
container:
95+
- "pkgs=`dpkg-query -W -f '${Package}\n'`"
96+
- "for p in $pkgs; do dpkg-query -f '${{source:Package}}\n' -W $p; done"
97+
delimiter: "\n"
98+
source_versions:
99+
invoke:
100+
1:
101+
container:
102+
- "pkgs=`dpkg-query -W -f '${Package}\n'`"
103+
- "for p in $pkgs; do dpkg-query -f '${{source:Version}}\n' -W $p; done"
104+
delimiter: "\n"
91105
copyrights:
92106
invoke:
93107
1:
@@ -229,6 +243,22 @@ rpm:
229243
container:
230244
- 'rpm --query --all --queryformat "%{license}\n" 2>/dev/null'
231245
delimiter: "\n"
246+
source_names:
247+
invoke:
248+
1:
249+
container:
250+
- "pkgs=`rpm --query --all --queryformat \"%{name}\n\" 2>/dev/null`"
251+
- |
252+
for p in $pkgs; do rpm -q --qf "%{SOURCERPM}\n" "$p" | sed 's/-[0-9].*\.src\.rpm$//'; done
253+
delimiter: "\n"
254+
source_versions:
255+
invoke:
256+
1:
257+
container:
258+
- "pkgs=`rpm --query --all --queryformat \"%{name}\n\" 2>/dev/null`"
259+
- |
260+
for p in $pkgs; do srcrpm="$(rpm -q --qf "%{SOURCERPM}\n" "$p")"; name="$( echo "${srcrpm}" | sed 's/-[0-9].*\.src\.rpm$//' )";version="$( echo "${srcrpm}" | sed "s/^${name}-\(.*\)\..*\.src\.rpm\$/\1/" )";echo "${version}"; done
261+
delimiter: "\n"
232262
files:
233263
invoke:
234264
1:

tern/analyze/default/command_lib/command_lib.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@
3636
with open(os.path.abspath(snippet_file), encoding='utf-8') as f:
3737
command_lib['snippets'] = yaml.safe_load(f)
3838
# list of package information keys that the command library can accomodate
39-
base_keys = {'names', 'versions', 'licenses', 'copyrights', 'proj_urls',
40-
'srcs', 'files'}
41-
package_keys = {'name', 'version', 'license', 'copyright', 'proj_url', 'src',
42-
'files'}
39+
base_keys = {'names', 'versions', 'licenses', 'source_names',
40+
'source_versions', 'copyrights', 'proj_urls', 'srcs', 'files'}
41+
package_keys = {'name', 'version', 'license', 'src_name', 'src_version',
42+
'copyright', 'proj_url', 'src', 'files'}
4343

4444
# global logger
4545
logger = logging.getLogger(constants.logger_name)

tern/classes/package.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class Package:
2222
checksum: checksum as package property
2323
files: list of files in a package
2424
pkg_licenses: all licenses found within a package
25+
src_name: the source package associated with the binary package
26+
src_version: the source package version
2527
2628
methods:
2729
to_dict: returns a dict representation of the instance
@@ -43,6 +45,8 @@ def __init__(self, name):
4345
self.__files = []
4446
self.__pkg_licenses = []
4547
self.__pkg_format = ''
48+
self.__src_name = ''
49+
self.__src_version = ''
4650

4751
@property
4852
def name(self):
@@ -124,6 +128,22 @@ def pkg_format(self):
124128
def pkg_format(self, pkg_format):
125129
self.__pkg_format = pkg_format
126130

131+
@property
132+
def src_name(self):
133+
return self.__src_name
134+
135+
@src_name.setter
136+
def src_name(self, src_name):
137+
self.__src_name = src_name
138+
139+
@property
140+
def src_version(self):
141+
return self.__src_version
142+
143+
@src_version.setter
144+
def src_version(self, src_version):
145+
self.__src_version = src_version
146+
127147
def get_file_paths(self):
128148
"""Return a list of paths of all the files in a package"""
129149
return [f.path for f in self.__files]
@@ -195,6 +215,8 @@ def fill(self, package_dict):
195215
copyright: <package copyright text>
196216
proj_url: <project url>
197217
files: <package files>
218+
src_name: <source package>
219+
src_ver: <source package version>
198220
the way to use this method is to instantiate the class with the
199221
name and then give it a package dictionary to fill in the rest
200222
return true if package name is the same as the one used to instantiate

tests/test_class_package.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def setUp(self):
2323
self.p1.checksum = '123abc456'
2424
self.p1.pkg_licenses = ['MIT', 'GPL']
2525
self.p1.pkg_format = 'deb'
26+
self.p1.src_name = 'p1src'
27+
self.p1.src_version = '1.0'
2628

2729
self.p2 = Package('p2')
2830

@@ -74,6 +76,8 @@ def testGetters(self):
7476
self.assertEqual(self.p1.checksum, '123abc456')
7577
self.assertEqual(self.p1.pkg_licenses, ['MIT', 'GPL'])
7678
self.assertEqual(self.p1.pkg_format, 'deb')
79+
self.assertEqual(self.p1.src_name, 'p1src')
80+
self.assertEqual(self.p1.src_version, '1.0')
7781

7882
def testAddFile(self):
7983
p1 = Package('package')
@@ -124,6 +128,8 @@ def testToDict(self):
124128
self.assertEqual(a_dict['files'][0]['path'], 'abc/pqr/test.java')
125129
self.assertEqual(a_dict['pkg_licenses'], ['MIT', 'GPL'])
126130
self.assertEqual(a_dict['pkg_format'], 'deb')
131+
self.assertEqual(a_dict['src_name'], 'p1src')
132+
self.assertEqual(a_dict['src_version'], '1.0')
127133

128134
def testToDictTemplate(self):
129135
template1 = TestTemplate1()
@@ -167,7 +173,10 @@ def testFill(self):
167173
'pkg_licenses': ['MIT', 'GPL'],
168174
'files': [{'name': 'a.txt', 'path': '/usr/a.txt'},
169175
{'name': 'b.txt', 'path': '/lib/b.txt'}],
170-
'pkg_format': 'rpm'}
176+
'pkg_format': 'rpm',
177+
'src_name': 'p1src',
178+
'src_version': '1.0'
179+
}
171180
p = Package('p1')
172181
p.fill(p_dict)
173182
self.assertEqual(p.name, 'p1')

0 commit comments

Comments
 (0)