-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial CycloneDX JSON support #1001
Conversation
@coderpatros I saw you joined the zoom call for the community meeting an hour early this morning. Do you have time join now? Apologies for the time confusion, we try to list it in UTC time since it gets confusing with US daylight savings. |
Here's the BOM I get when running with these changes for the Details{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"serialNumber": "urn:uuid:0d512c96-3f6f-4540-8970-c8d3811b51d1",
"version": 1,
"metadata": {
"timestamp": "2021-07-21T22:37:44Z",
"tools": [
{
"vendor": "Tern Tools",
"name": "Tern",
"version": "3181e350efd42f9efabe54b31fa934d95ebe1ee7"
}
],
"component": {
"type": "container",
"scope": "required",
"name": "photon",
"version": "fbdae32f534858727fa855af8d548dfa5d98872ef81f466790f7c302a46e8384",
"hashes": [
{
"SHA-256": "fbdae32f534858727fa855af8d548dfa5d98872ef81f466790f7c302a46e8384"
}
],
"properties": {
"repotags": [
"photon:3.0"
]
},
"components": [
{
"type": "container",
"scope": "required",
"name": 1,
"version": "b51a3a7866bca4b8096578a679e302c22afd6dc5d95299b3c17364c9639bb912",
"hashes": [
{
"SHA-256": "b51a3a7866bca4b8096578a679e302c22afd6dc5d95299b3c17364c9639bb912"
}
],
"properties": {
"pkg_format": "rpm",
"os_guess": "VMware Photon OS/Linux"
}
}
]
}
},
"components": [
{
"name": "bash",
"version": "4.4.18-2.ph3"
},
{
"name": "bzip2-libs",
"version": "1.0.8-1.ph3"
},
{
"name": "ca-certificates",
"version": "20190521-1.ph3"
},
{
"name": "ca-certificates-pki",
"version": "20190521-1.ph3"
},
{
"name": "curl",
"version": "7.61.1-6.ph3"
},
{
"name": "curl-libs",
"version": "7.61.1-6.ph3"
},
{
"name": "e2fsprogs-libs",
"version": "1.45.5-1.ph3"
},
{
"name": "elfutils-libelf",
"version": "0.176-1.ph3"
},
{
"name": "expat",
"version": "2.2.9-1.ph3"
},
{
"name": "expat-libs",
"version": "2.2.9-1.ph3"
},
{
"name": "filesystem",
"version": "1.1-4.ph3"
},
{
"name": "glibc",
"version": "2.28-5.ph3"
},
{
"name": "krb5",
"version": "1.17-1.ph3"
},
{
"name": "libcap",
"version": "2.25-8.ph3"
},
{
"name": "libdb",
"version": "5.3.28-2.ph3"
},
{
"name": "libgcc",
"version": "7.3.0-4.ph3"
},
{
"name": "libmetalink",
"version": "0.1.3-1.ph3"
},
{
"name": "libsolv",
"version": "0.6.35-3.ph3"
},
{
"name": "libssh2",
"version": "1.9.0-2.ph3"
},
{
"name": "lua",
"version": "5.3.5-2.ph3"
},
{
"name": "ncurses-libs",
"version": "6.1-2.ph3"
},
{
"name": "nspr",
"version": "4.21-1.ph3"
},
{
"name": "nss-libs",
"version": "3.44-3.ph3"
},
{
"name": "openssl",
"version": "1.0.2u-2.ph3"
},
{
"name": "photon-release",
"version": "3.0-5.ph3"
},
{
"name": "photon-repos",
"version": "3.0-5.ph3"
},
{
"name": "popt",
"version": "1.16-5.ph3"
},
{
"name": "readline",
"version": "7.0-2.ph3"
},
{
"name": "rpm-libs",
"version": "4.14.2-7.ph3"
},
{
"name": "sqlite-libs",
"version": "3.31.1-4.ph3"
},
{
"name": "tdnf",
"version": "2.1.0-3.ph3"
},
{
"name": "tdnf-cli-libs",
"version": "2.1.0-3.ph3"
},
{
"name": "toybox",
"version": "0.8.2-1.ph3"
},
{
"name": "xz-libs",
"version": "5.2.4-1.ph3"
},
{
"name": "zlib",
"version": "1.2.11-1.ph3"
}
]
}
|
With the current state of this PR that looks about right. It still needs work and QA on my part. |
389aa17
to
564257a
Compare
Example output: {
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"serialNumber": "urn:uuid:5fed8dae-3e13-44a1-903c-1b3fdb98bd18",
"version": 1,
"metadata": {
"timestamp": "2021-07-26T20:14:36Z",
"tools": [
{
"vendor": "Tern Tools",
"name": "Tern",
"version": "389aa17f9137eb34c0ff3bc9e0040d7e1d79498f"
}
],
"component": {
"type": "container",
"scope": "required",
"name": "docker",
"version": "sha256:a61102937d2bda8319882998ef1ffa27387617f6eea6c298b18a05f7fba82c0d",
"hashes": [
{
"alg": "SHA-256",
"content": "a61102937d2bda8319882998ef1ffa27387617f6eea6c298b18a05f7fba82c0d"
}
],
"properties": [
{
"name": "tern:repotag",
"value": "docker:latest"
},
{
"name": "tern:os_guess",
"value": "Alpine Linux v3.13"
}
],
"purl": "pkg:docker/docker@sha256:a61102937d2bda8319882998ef1ffa27387617f6eea6c298b18a05f7fba82c0d"
}
},
"components": [
{
"name": "musl",
"version": "1.2.2-r0",
"type": "application",
"purl": "pkg:apk/alpine/musl@1.2.2-r0",
"licenses": [
{
"license": {
"name": "MIT"
}
}
]
},
{
"name": "busybox",
"version": "1.32.1-r6",
"type": "application",
"purl": "pkg:apk/alpine/busybox@1.32.1-r6",
"licenses": [
{
"license": {
"name": "GPL-2.0-only"
}
}
]
},
{
"name": "alpine-baselayout",
"version": "3.2.0-r8",
"type": "application",
"purl": "pkg:apk/alpine/alpine-baselayout@3.2.0-r8",
"licenses": [
{
"license": {
"name": "GPL-2.0-only"
}
}
]
},
{
"name": "alpine-keys",
"version": "2.2-r0",
"type": "application",
"purl": "pkg:apk/alpine/alpine-keys@2.2-r0",
"licenses": [
{
"license": {
"name": "MIT"
}
}
]
},
{
"name": "libcrypto1.1",
"version": "1.1.1k-r0",
"type": "application",
"purl": "pkg:apk/alpine/libcrypto1.1@1.1.1k-r0",
"licenses": [
{
"license": {
"name": "OpenSSL"
}
}
]
},
{
"name": "libssl1.1",
"version": "1.1.1k-r0",
"type": "application",
"purl": "pkg:apk/alpine/libssl1.1@1.1.1k-r0",
"licenses": [
{
"license": {
"name": "OpenSSL"
}
}
]
},
{
"name": "ca-certificates-bundle",
"version": "20191127-r5",
"type": "application",
"purl": "pkg:apk/alpine/ca-certificates-bundle@20191127-r5",
"licenses": [
{
"license": {
"name": "MPL-2.0 AND MIT"
}
}
]
},
{
"name": "libtls-standalone",
"version": "2.9.1-r1",
"type": "application",
"purl": "pkg:apk/alpine/libtls-standalone@2.9.1-r1",
"licenses": [
{
"license": {
"name": "ISC"
}
}
]
},
{
"name": "ssl_client",
"version": "1.32.1-r6",
"type": "application",
"purl": "pkg:apk/alpine/ssl_client@1.32.1-r6",
"licenses": [
{
"license": {
"name": "GPL-2.0-only"
}
}
]
},
{
"name": "zlib",
"version": "1.2.11-r3",
"type": "application",
"purl": "pkg:apk/alpine/zlib@1.2.11-r3",
"licenses": [
{
"license": {
"name": "Zlib"
}
}
]
},
{
"name": "apk-tools",
"version": "2.12.5-r0",
"type": "application",
"purl": "pkg:apk/alpine/apk-tools@2.12.5-r0",
"licenses": [
{
"license": {
"name": "GPL-2.0-only"
}
}
]
},
{
"name": "scanelf",
"version": "1.2.8-r0",
"type": "application",
"purl": "pkg:apk/alpine/scanelf@1.2.8-r0",
"licenses": [
{
"license": {
"name": "GPL-2.0-only"
}
}
]
},
{
"name": "musl-utils",
"version": "1.2.2-r0",
"type": "application",
"purl": "pkg:apk/alpine/musl-utils@1.2.2-r0",
"licenses": [
{
"license": {
"name": "MIT BSD GPL2+"
}
}
]
},
{
"name": "libc-utils",
"version": "0.7.2-r3",
"type": "application",
"purl": "pkg:apk/alpine/libc-utils@0.7.2-r3",
"licenses": [
{
"license": {
"name": "BSD-2-Clause AND BSD-3-Clause"
}
}
]
},
{
"name": "ca-certificates",
"version": "20191127-r5",
"type": "application",
"purl": "pkg:apk/alpine/ca-certificates@20191127-r5",
"licenses": [
{
"license": {
"name": "MPL-2.0 AND MIT"
}
}
]
},
{
"name": "openssh-keygen",
"version": "8.4_p1-r3",
"type": "application",
"purl": "pkg:apk/alpine/openssh-keygen@8.4_p1-r3",
"licenses": [
{
"license": {
"name": "BSD"
}
}
]
},
{
"name": "ncurses-terminfo-base",
"version": "6.2_p20210109-r0",
"type": "application",
"purl": "pkg:apk/alpine/ncurses-terminfo-base@6.2_p20210109-r0",
"licenses": [
{
"license": {
"name": "MIT"
}
}
]
},
{
"name": "ncurses-libs",
"version": "6.2_p20210109-r0",
"type": "application",
"purl": "pkg:apk/alpine/ncurses-libs@6.2_p20210109-r0",
"licenses": [
{
"license": {
"name": "MIT"
}
}
]
},
{
"name": "libedit",
"version": "20191231.3.1-r1",
"type": "application",
"purl": "pkg:apk/alpine/libedit@20191231.3.1-r1",
"licenses": [
{
"license": {
"name": "BSD-3-Clause"
}
}
]
},
{
"name": "openssh-client",
"version": "8.4_p1-r3",
"type": "application",
"purl": "pkg:apk/alpine/openssh-client@8.4_p1-r3",
"licenses": [
{
"license": {
"name": "BSD"
}
}
]
}
]
} |
Output can be validated using either the CycloneDX CLI or Web Tool. |
@sameer1046 I've taken a minimalist approach to this initial support. Including the information I am most interested in. But I'd appreciate your feedback on what is/is not included in the above example output. |
At this moment the BOM looks nice. Thank you very much. |
@coderpatros Thanks for your work on this! I was on vacation last week. I will review the PR this week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderpatros This mostly looks good to me! I have a few questions. Also, if you could add a body to commit 2 in your commit series, that would be awesome :)
Thank you so much for this work!
tern/formats/cyclonedx/cyclonedx.py
Outdated
@@ -0,0 +1,24 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (c) 2019-2020 VMware, Inc. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can add your own Copyright here Copyright (c) 2021 Patrick Dwyer
if you want.
tern/formats/cyclonedx/cyclonedx.py
Outdated
from tern.classes.template import Template | ||
|
||
|
||
class CycloneDX(Template): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The template is meant to provide a mapping from Tern's metadata field names to CycloneDX's field names. It looks like whatever property names Tern uses, CycloneDX uses (name
and version
). So you don't need this file.
'pip': 'pip', | ||
'gem': 'gem', | ||
'npm': 'npm', | ||
'go.mod': 'go', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If go.mod
is the only one that is different for purl, you can change the pkg_format
in tern/analyze/default/command_lib/base.yml
:
go:
pkg_format: 'go.mod' <-- change this to 'go'
Then you will not need this mapping.
|
||
|
||
# map Tern OS guesses to package URL namespace | ||
os_guess_purl_namespace_mapping = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if there is an easier way to figure out a valid global namespace for all the ecosystems. Does CycloneDX require a specific string format for namespace or can we just do something like replace spaces with underscores?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is determined by the package URL spec. This might have been me over thinking future OS support. I'll refactor this part and we can always come back in the future and implement an override mapping for any exceptions instead.
|
||
|
||
def get_os_guess(image_obj): | ||
for layer in image_obj.layers: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just return image_obj.layers[0].os_guess
if it's not empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that might have been the case. Thanks
@@ -0,0 +1,4 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (c) 2021 VMware, Inc. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use your own Copyright here.
logger = logging.getLogger(constants.logger_name) | ||
|
||
|
||
def get_document_dict(image_obj_list, template): # pylint: disable=[unused-argument] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're not using template
you can remove it.
@coderpatros One more thing: Last tern community meeting notes mention that Rose was concerned about long term maintenance of this format. Do you have any suggestions on how the Tern community can get support for any issues that come up? We do the same thing for SPDX and both the extensions. |
Backwards compatibility is very important to us. So there shouldn't be much maintenance created by new versions of the spec, unless you want to take advantage of a new capability. I think the more likely cause of maintenance overhead is if you want to refactor Tern or change the Tern data model. That's one of the reasons I've kept this as a minimal implementation. So it only has core information, and in the future, information requested/added by Tern users. The CycloneDX Slack is probably the best place to ask any general questions. But there's also a mailing list. But if you get any users raise CycloneDX related issues I'm also happy for you to tag me in the issue to provide feedback.
After I make the above changes I could push this back up as a single commit if you like? I didn't want to change the existing commit with the README one in case you were already part way through reviewing it. |
Will you be able to provide a list of missing fields? We could include them in Tern's data model.
Thank you!
The README looks great! You can create one commit :). |
A lot of them are hard to populate outside of the build process. But if there is something that I think would be useful I'll raise separate issues for them. |
@nishakm I've made some changes. I've kept them as a separate commit to make it easier to review. But if you're happy with them I'll squash all the commits into a single commit with a more complete commit message before you merge them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I'd appreciate it if you could squash your changes into one commit. Sorry for getting back to you so late!
@@ -434,7 +434,7 @@ npm: | |||
delimiter: "LICF" | |||
# golang---------------------------------------------------------------------- | |||
go: | |||
pkg_format: 'go.mod' | |||
pkg_format: 'go' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
- support single or multiple images in output - include basic component information (name, version, hashes, license) - include license evidence - report components by image - add packageurl-python as a dependency - change `go.mod` pkg_format to `go` to align to Package URL spec - include repotag and os_guess as custom properties - update README Signed-off-by: Patrick Dwyer <patrick.dwyer@owasp.org>
No worries @nishakm, we've all got other things going on in our lives. Should be good to go now. |
@coderpatros Looks like something's broken on main. Let me merge in a fix and get back to this. |
Demo support for #987
There's heaps missing from this. Just implemented the absolute basics.
A few issues (while they are fresh in my mind):
repotags
, but plenty of extra tern metadata has not been captured