DependencyTrack / dependency-track

Dependency-Track is an intelligent Component Analysis platform that allows organizations to identify and reduce risk in the software supply chain.
https://dependencytrack.org/
Apache License 2.0
2.66k stars 571 forks source link

Add support for multiple licenses #170

Open stevespringett opened 6 years ago

stevespringett commented 6 years ago

Components are often released under multiple licenses. SPDX license expressions provide this, and other capabilities.

https://github.com/CycloneDX/specification/issues/1

stevespringett commented 6 years ago

This could be as simple as supporting SPDX expressions rather than changing table license/component relationships from 1-M to N-M.

stevespringett commented 5 years ago

Investigate use of:

mariuskimmina commented 2 years ago

Is there anything I can do here as a workaround when using the cyclonedx-bom (python) tool? I don't have any use-case for multiple licenses it's just single licenses showing up in this expression format such as

{
      "type": "library",
      "bom-ref": "f98ac10c-6596-417e-b518-3a98cef0c2da",
      "author": "A lot of people",
      "name": "pyflakes",
      "version": "2.3.1",
      "licenses": [
        {
          "expression": "MIT"
        }
      ],
      "purl": "pkg:pypi/pyflakes@2.3.1"
    }
stevespringett commented 2 years ago

you could try to fork the python library and change the code to emit either spdx license id or unresolved license names. If you didn't want to do that, you could create a transformation utility that takes the existing format and does the same conversion. Other than that, not sure what else can be done.

jonaz commented 2 years ago

trivy also put it in expression trivy image --format cyclonedx --output result.json alpine:3.15

            "bom-ref": "pkg:apk/alpine/alpine-keys@2.4-r1?distro=3.15.3",
            "type": "library",
            "name": "alpine-keys",
            "version": "2.4-r1",
            "licenses": [
                {
                    "expression": "MIT"
                }
            ],

it seems to always ever be one lincense in there: https://github.com/aquasecurity/trivy/blob/7a148089ece78746f363d5358048d9a74599cedf/pkg/report/cyclonedx/cyclonedx.go#L291-L295

mariuskimmina commented 2 years ago

If it helps, I wrote a short python script for the conversion - https://gist.github.com/mariuskimmina/4d78f4d776b5ac5cecf408f08919e156 This probably fails in a lot of cases (like when there are acctually multiple licenses) but it worked for my simple case - getting the single licenses to display in dependencytrack

Jonas-vdb commented 2 years ago

The conversion of the SPDX expression to the SPDX name property (using a script) does show the license in DT but a lot of the licenses are not detected by DT which causes the Policy Management rules to fail.

kishankarun commented 2 years ago

Is it planned to support the expressions format for licenses ?

"licenses": [
                {
                    "expression": "MIT"
                }
]
stevespringett commented 2 years ago

Yes @kishankarun. SPDX expression support is planned as part of this ticket.

jlongo-encora commented 2 years ago

@stevespringett Do you have any update/estimate about when this ticket will be implemented?

stevespringett commented 2 years ago

@jlongo-encora No ETA at this point. It's not targeted towards any milestone yet. I just added a help wanted tag to the ticket.

software-testing-professional commented 2 years ago

:disappointed: This feature is much awaited by our dev teams. Especially in combination with https://github.com/DependencyTrack/dependency-track/issues/1732 it would be a huge improvement regarding OSS license clearing.

Sadly I can't support the implementation, because this is beyond my developer skills. :sweat_smile:

For now, as a workaround, I'll restructure the CycloneDX files before uploading.

Approach: If a component has more than one detected license, I duplicate it into separate components. One per license. The first one keeps the original name, cpe and purl. And all the clones get the cpe and purl removed. The name is modified to <component-name> (additional license). This seems necessary, because otherwise DependencyTrack would remove components with identical purl / cpe.

image

This gives me the ability to

image

The "downside" of this is, that the total number of components is slightly increased. But that's ok for our use case.

CycloneDX (modified):

"components" : [
{
      "name" : "apk-tools",
      "version" : "2.12.1-r0",
      "description" : "Alpine Package Keeper - package manager for alpine",
      "licenses" : [
        {
          "license" : {
            "id" : "GPL-2.0-only"
          }
        }
      ],
      "cpe" : "cpe:2.3:a:apk-tools:apk-tools:2.12.1-r0:*:*:*:*:*:*:*",
      "purl" : "pkg:alpine/apk-tools@2.12.1-r0?arch=x86_64&distro=alpine-3.13.2&upstream=apk-tools",
      "externalReferences" : [
        {
          "type" : "distribution",
          "url" : "https://gitlab.alpinelinux.org/alpine/apk-tools"
        }
      ],
      "type" : "library"
    },
    {
      "name" : "apk-tools (additional license)",
      "group": "oss.clearing",
      "version" : "2.12.1-r0",
      "description" : "Alpine Package Keeper - package manager for alpine",
      "licenses" : [
        {
          "license" : {
            "id" : "Apache-2.0"
          }
        }
      ],
      "externalReferences" : [
        {
          "type" : "distribution",
          "url" : "https://gitlab.alpinelinux.org/alpine/apk-tools"
        }
      ],
      "type" : "library"
    }
  ]
}

Maybe this approach is also a suitable workaround for other users. :smiley:

keymandll commented 2 years ago

I'm having the same issue. It is impossible to manage licenses of Rust projects because many of the projects are dual-licensed, e.g. MIT or Apache-2.0. This format is fine by SPDX, but unfortunately, there is no good way to deal with this with Dependency Track.

Regarding the above-mentioned workaround, I created a similar script to do the conversion. What is weird is that even though all the components in my case ended up with uniform licenses (see XML below), only the last component had the license showing up on the UI. (see attached screenshot) I'm not entirely sure why <licenses><license><id>MIT</id></license></licenses> is OK and processed fine for the last component, but not for the others. Is this a bug, or am I missing something?

Screenshot 2022-10-26 at 16 13 34
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:a1a3a06f-0863-462f-9f98-cdcb1453460a" version="1">
  <metadata>
    <timestamp>2022-10-26T14:07:05.411453000Z</timestamp>
    <tools>
      <tool>
        <vendor>CycloneDX</vendor>
        <name>cargo-cyclonedx</name>
        <version>0.3.7</version>
      </tool>
    </tools>
    <component type="application" bom-ref="pkg:cargo/dtrack@0.1.0">
      <name>dtrack</name>
      <version>0.1.0</version>
      <scope>required</scope>
      <licenses>
        <expression>MIT</expression>
      </licenses>
      <purl>pkg:cargo/dtrack@0.1.0</purl>
    </component>
  </metadata>
  <components>
    <component type="library" bom-ref="pkg:cargo/argparse@0.2.2">
      <name>argparse</name>
      <version>0.2.2</version>
      <description>Powerful command-line argument parsing library</description>
      <scope>required</scope>
      <licenses><license><id>MIT</id></license></licenses>
      <purl>pkg:cargo/argparse@0.2.2</purl>
      <externalReferences>
        <reference type="website">
          <url>http://github.com/tailhook/rust-argparse</url>
        </reference>
      </externalReferences>
    </component>
    <component type="library" bom-ref="pkg:cargo/base64@0.13.1">
      <name>base64</name>
      <version>0.13.1</version>
      <description>encodes and decodes base64 as bytes or utf8</description>
      <scope>required</scope>
      <licenses><license><id>MIT</id></license></licenses>
      <purl>pkg:cargo/base64@0.13.1</purl>
      <externalReferences>
        <reference type="documentation">
          <url>https://docs.rs/base64</url>
        </reference>
        <reference type="vcs">
          <url>https://github.com/marshallpierce/rust-base64</url>
        </reference>
      </externalReferences>
    </component>
    <component type="library" bom-ref="pkg:cargo/regex@1.6.0">
      <name>regex</name>
      <version>1.6.0</version>
      <description>An implementation of regular expressions for Rust. This implementation uses finite automata and guarantees linear time matching on all inputs. </description>
      <scope>required</scope>
      <licenses><license><id>MIT</id></license></licenses>
      <purl>pkg:cargo/regex@1.6.0</purl>
      <externalReferences>
        <reference type="documentation">
          <url>https://docs.rs/regex</url>
        </reference>
        <reference type="website">
          <url>https://github.com/rust-lang/regex</url>
        </reference>
        <reference type="vcs">
          <url>https://github.com/rust-lang/regex</url>
        </reference>
      </externalReferences>
    </component>
    <component type="library" bom-ref="pkg:cargo/reqwest@0.11.12">
      <name>reqwest</name>
      <version>0.11.12</version>
      <description>higher level HTTP client library</description>
      <scope>required</scope>
      <licenses><license><id>MIT</id></license></licenses>
      <purl>pkg:cargo/reqwest@0.11.12</purl>
      <externalReferences>
        <reference type="documentation">
          <url>https://docs.rs/reqwest</url>
        </reference>
        <reference type="vcs">
          <url>https://github.com/seanmonstar/reqwest</url>
        </reference>
      </externalReferences>
    </component>
    <component type="library" bom-ref="pkg:cargo/serde@1.0.147">
      <name>serde</name>
      <version>1.0.147</version>
      <description>A generic serialization/deserialization framework</description>
      <scope>required</scope>
      <licenses><license><id>MIT</id></license></licenses>
      <purl>pkg:cargo/serde@1.0.147</purl>
      <externalReferences>
        <reference type="documentation">
          <url>https://docs.serde.rs/serde/</url>
        </reference>
        <reference type="website">
          <url>https://serde.rs</url>
        </reference>
        <reference type="vcs">
          <url>https://github.com/serde-rs/serde</url>
        </reference>
      </externalReferences>
    </component>
    <component type="library" bom-ref="pkg:cargo/serde_json@1.0.87">
      <name>serde_json</name>
      <version>1.0.87</version>
      <description>A JSON serialization file format</description>
      <scope>required</scope>
      <licenses><license><id>MIT</id></license></licenses>
      <purl>pkg:cargo/serde_json@1.0.87</purl>
      <externalReferences>
        <reference type="documentation">
          <url>https://docs.serde.rs/serde_json/</url>
        </reference>
        <reference type="vcs">
          <url>https://github.com/serde-rs/json</url>
        </reference>
      </externalReferences>
    </component>
    <component type="library" bom-ref="pkg:cargo/url@2.3.1">
      <name>url</name>
      <version>2.3.1</version>
      <description>URL library for Rust, based on the WHATWG URL Standard</description>
      <scope>required</scope>
      <licenses><license><id>MIT</id></license></licenses>
      <purl>pkg:cargo/url@2.3.1</purl>
      <externalReferences>
        <reference type="documentation">
          <url>https://docs.rs/url</url>
        </reference>
        <reference type="vcs">
          <url>https://github.com/servo/rust-url</url>
        </reference>
      </externalReferences>
    </component>
  </components>
</bom>
strowi commented 1 year ago

Also looking forward to this. Recently integrated trivy as a scanning solution and had to find out the hard way after debugging for a while that it is a problem with the format.

ataraxus commented 1 year ago

This issue bit me also right now. Would be wonderfull if there were some way to circumvent this.

nscuro commented 1 year ago

@ataraxus Which issue specifically? DT v4.9 added initial support for SPDX expressions. They are now ingested from uploaded SBOMs and can be used in policies (see https://docs.dependencytrack.org/usage/policy-compliance/#license-violation).

ataraxus commented 1 year ago

@nscuro thanks for pointing this out! I just confirmed we have 4.8 installed...

I have the issue, that DT doesnt detect any license when cyclone produces this:

"licenses": [
        {
          "expression": "MIT OR Apache-2.0"
        }
      ],
nscuro commented 1 year ago

In that case, give v4.9 a try. Without question we still have room for improvement, hence this ticket not being closed yet.

But expressions are now taken correctly and displayed in the UI.

ansonallard commented 1 year ago

Hello all - a question based on the 4.9.0 release.

I have an npm component: type-fest 0.20.2 that has a SPDX license expression on the public registry.

I was expecting that SPDX expression to appear in the frontend under the SPDX expression, since an expression was returned from the backend:

Component DTO (GET /v1/component/{id}):

{
  "name": "type-fest",
  "version": "0.20.2",
  "classifier": "LIBRARY",
  "sha512": "35ef9e138af4fe25a7a40c43f39db3dc0f8dd01b7944dfff36327045dd95147126af2c317f9bec66587847a962c65e81fb0cfff1dfa669348090dd452242372d",
  "purl": "pkg:npm/type-fest@0.20.2",
  "purlCoordinates": "pkg:npm/type-fest@0.20.2",
  "license": "(MIT OR CC0-1.0)",
  "project": {
    "name": "test",
    "version": "3",
    "classifier": "APPLICATION",
    "uuid": "8c8288e2-cd99-49d0-a8bb-1260a262a26c",
    "lastBomImport": 1698332194744,
    "lastBomImportFormat": "CycloneDX 1.4",
    "lastInheritedRiskScore": 6,
    "active": true
  },
  "lastInheritedRiskScore": 0,
  "uuid": "6c55ba56-4265-4011-937b-1fde60960e89",
  "usedBy": 0,
  "expandDependencyGraph": false,
  "isInternal": false
}

When I went to the component in the frontend, the SPDX expression was blank: image

Is this the expected behavior?

ansonallard commented 1 year ago

Additionally, after reading and following the policy recommendation, I wrote a test policy to disallow all licenses outside of a certain license group - in this case, the permissive license group.

image

The permissive license group contains the MIT License.

Based on the SPDX expression of my test component, type-fest, which has the following license SPDX expression: (MIT OR CC0-1.0), I would have expected my policy to not apply to this component. However, the policy was triggered.

image

(GET /v1/violation/project/{id}):

{
  "type": "LICENSE",
  "project": {
    "name": "test",
    "version": "3",
    "classifier": "APPLICATION",
    "uuid": "8c8288e2-cd99-49d0-a8bb-1260a262a26c",
    "properties": [],
    "tags": [],
    "lastBomImport": 1698332194744,
    "lastBomImportFormat": "CycloneDX 1.4",
    "lastInheritedRiskScore": 6,
    "active": true
  },
  "component": {
    "name": "type-fest",
    "version": "0.20.2",
    "classifier": "LIBRARY",
    "sha512": "35ef9e138af4fe25a7a40c43f39db3dc0f8dd01b7944dfff36327045dd95147126af2c317f9bec66587847a962c65e81fb0cfff1dfa669348090dd452242372d",
    "purl": "pkg:npm/type-fest@0.20.2",
    "purlCoordinates": "pkg:npm/type-fest@0.20.2",
    "license": "(MIT OR CC0-1.0)",
    "project": {
      "name": "test",
      "version": "3",
      "classifier": "APPLICATION",
      "uuid": "8c8288e2-cd99-49d0-a8bb-1260a262a26c",
      "properties": [],
      "tags": [],
      "lastBomImport": 1698332194744,
      "lastBomImportFormat": "CycloneDX 1.4",
      "lastInheritedRiskScore": 6,
      "active": true
    },
    "lastInheritedRiskScore": 0,
    "uuid": "6c55ba56-4265-4011-937b-1fde60960e89",
    "usedBy": 0,
    "expandDependencyGraph": false,
    "isInternal": false
  },
  "policyCondition": {
    "policy": {
      "name": "disallow non-permissive",
      "operator": "ANY",
      "violationState": "FAIL",
      "uuid": "7f6a875f-9341-4389-89a9-259fecdd5a1a",
      "includeChildren": false,
      "global": true
    },
    "operator": "IS_NOT",
    "subject": "LICENSE_GROUP",
    "value": "55a6d79e-304b-4cba-a3f9-5bef3aaf3988",
    "uuid": "bd26187b-f643-4127-82e3-3d69f6b30a1a"
  },
  "timestamp": 1698332196090,
  "uuid": "2267f550-9a57-452c-8080-b6c864fbd5e8"
}

Is this the expected behavior - meaning I'm misunderstanding how policies are evaluated against SPDX expressions?

nscuro commented 1 year ago

@ansonallard For the SPDX expression feature to work, the expression must be present in the licenseExpression field. In the API response you shared, it's in the license field. Did you re-import your BOM after upgrading to 4.9.0?

Additionally, how does the SBOM look that you're uploading? The expression should be in the component.licenses[].expression field.

ansonallard commented 1 year ago

The BOM upload was fresh after upgrading to 4.9.0.

It looks like my BOM is incorrect, as this is the component that was pushed to dependency track (CycloneDX 1.4):

    {
      "group": "",
      "name": "type-fest",
      "version": "0.20.2",
      "hashes": [
        {
          "alg": "SHA-512",
          "content": "35ef9e138af4fe25a7a40c43f39db3dc0f8dd01b7944dfff36327045dd95147126af2c317f9bec66587847a962c65e81fb0cfff1dfa669348090dd452242372d"
        }
      ],
      "licenses": [
        {
          "license": {
            "name": "(MIT OR CC0-1.0)"
          }
        }
      ],
      "purl": "pkg:npm/type-fest@0.20.2",
      "type": "library",
      "bom-ref": "pkg:npm/type-fest@0.20.2",
      "properties": [
        {
          "name": "SrcFile",
          "value": "/app/package-lock.json"
        }
      ]
    },
nscuro commented 1 year ago

May I ask what tool you used to generate the SBOM?

ansonallard commented 1 year ago

I used cdxgen.

ansonallard commented 1 year ago

After reviewing the CycloneDX 1.4 spec, it seems this tool does not generate the correct SBOM output given a SPDX expression, as defined here.

ansonallard commented 1 year ago

After uploading a BOM file with the correct "expression" key/value pair in the license list, I was able to verify that dependency track correctly handles the SPDX expression. I'll raise the issue to the maintainer of my SBOM generation tool. Thank you @nscuro for your insights.

Souhila99 commented 9 months ago

Hi @nscuro, Does Dependency Track consider license Expressions during policy evaluation ? I uploaded a SBOM which contains component with multiple licenses by using SPDX expression, the SPDX expression is shown in the GUI but the policy evaluation doesn't take into account the licenses i put in the license expression. Here's the component information in the SBOM `

packageName 1.1.1 Apache-2.0 AND MIT pkg:generic/packageName@1.1.1

` I create License policy and added the Apache-2.0 to the forbidden licenses Group to test if it's taken into account but i don't see any license violation for this component.

jerbob92 commented 5 months ago

@strowi and @ataraxus, is this still working for you in Trivy? Trivy now doesn't seem to create expression anymore in the cyclonedx format, my licenses now look like this:

      "licenses": [
        {
          "license": {
            "name": "Apache-2.0"
          }
        }
      ]

And it looks like DT doesn't do anything with that format.

Also started a discussion at Trivy here: https://github.com/aquasecurity/trivy/discussions/6761

jerbob92 commented 5 months ago

It seems that using the name has been implemented here: https://github.com/DependencyTrack/dependency-track/pull/3555

Any idea why that's not working here @nscuro or @aravindparappil46

jerbob92 commented 5 months ago

Nevermind me, locally Trivy was adding the license info fine, but it was not adding it at all in my production environment, so that's why it wasn't showing up.

guyscher2 commented 2 months ago

Hey, I'm having an issue with deptrack to display multiple licenses in cyclondx 1.5 format. The licenses are not an expression but an array of licenses. Deptrack takes only the latest license in the array and displays it.

Expected behavior: Display all possible licenses - perhaps convert them to an expression and display it that way.

The sbom (left only the problematic dependency):

    "bomFormat": "CycloneDX",
    "specVersion": "1.5",
    "serialNumber": "urn:uuid:8892617a-60e7-42e1-abb5-bda1f451e960",
    "version": 1,
    "metadata": {
        "timestamp": "2024-08-06T13:33:56Z",
        "tools": {
            "components": [
                {
                    "group": "@cyclonedx",
                    "name": "cdxgen",
                    "version": "10.9.2",
                    "purl": "pkg:npm/%40cyclonedx/cdxgen@10.9.2",
                    "type": "application",
                    "bom-ref": "pkg:npm/@cyclonedx/cdxgen@10.9.2",
                    "author": "OWASP Foundation",
                    "publisher": "OWASP Foundation"
                }
            ]
        },
        "authors": [
            {
                "name": "OWASP Foundation"
            }
        ],
        "lifecycles": [
            {
                "phase": "build"
            }
        ],
        "component": {
            "name": "project-name",
            "type": "application",
            "group": "com.company",
            "version": "0.0.1-LOCAL",
            "properties": [
                {
                    "name": "buildFile",
                    "value": "/app/build.gradle"
                },
                {
                    "name": "projectDir",
                    "value": "/app"
                },
                {
                    "name": "rootDir",
                    "value": "/app"
                }
            ],
            "purl": "pkg:maven/com.company/project-name@0.0.1-LOCAL?type=jar",
            "bom-ref": "pkg:maven/com.company/project-name@0.0.1-LOCAL?type=jar",
            "components": [
                {
                    "group": "",
                    "name": "app",
                    "version": "latest",
                    "type": "application",
                    "bom-ref": "pkg:gem/app@latest",
                    "purl": "pkg:gem/app@latest"
                }
            ]
        },
        "properties": [
            {
                "name": "cdx:bom:componentTypes",
                "value": "maven"
            },
            {
                "name": "cdx:bom:componentNamespaces",
                "value": "commons-io\\njakarta.servlet\\norg.apache.commons\\norg.apache.logging.log4j\\norg.apache.santuario\\norg.junit.jupiter\\norg.junit.platform\\norg.opensaml"
            }
        ]
    },
    "components": [
        {
            "publisher": "Eclipse Foundation",
            "group": "jakarta.servlet",
            "name": "jakarta.servlet-api",
            "version": "5.0.0",
            "description": "\n        Eclipse Enterprise for Java (EE4J) is an open source initiative to create standard APIs,\n        implementations of those APIs, and technology compatibility kits for Java runtimes\n        that enable development, deployment, and management of server-side and cloud-native applications.\n    ",
            "licenses": [
                {
                    "license": {
                        "id": "EPL-2.0",
                        "url": "https://opensource.org/licenses/EPL-2.0"
                    }
                },
                {
                    "license": {
                        "id": "GPL-2.0-with-classpath-exception",
                        "url": "https://opensource.org/licenses/GPL-2.0-with-classpath-exception"
                    }
                }
            ],
            "purl": "pkg:maven/jakarta.servlet/jakarta.servlet-api@5.0.0?type=jar",
            "externalReferences": [
                {
                    "type": "vcs",
                    "url": "https://github.com/eclipse-ee4j/servlet-api"
                }
            ],
            "type": "library",
            "bom-ref": "pkg:maven/jakarta.servlet/jakarta.servlet-api@5.0.0?type=jar",
            "properties": [
                {
                    "name": "GradleProfileName",
                    "value": "compileClasspath"
                }
            ]
        }
    ],
    "services": [],
    "dependencies": [
        "not relevant"
    ]
}

dep track:

image image image