Skip to content
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

Add support for 1.4 to cyclonedx-bom #399

Closed
wants to merge 28 commits into from
Closed

Conversation

tokcum
Copy link
Contributor

@tokcum tokcum commented May 18, 2023

Testing if this PR meets the expectations.

@tokcum tokcum requested a review from a team as a code owner May 18, 2023 19:28
allisonsierra and others added 13 commits May 18, 2023 21:31
Signed-off-by: Allie Sierra <85902135+allisonsierra@users.noreply.github.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
…ng From, interim fix in specs.v1_3.component marked as todo

Signed-off-by: tokcum <tobias.mucke@gmail.com>
* Update dependencies

- base64 deprecated the functions, so replacing them with the equivalent engine
- also update flake and formatting
- fix new clippy lints
- fix MSRV due to SPDX license change

Signed-off-by: Amy Keibler <amelia.keibler@gmail.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
…cloneDX#365)

* refactor: show a warning when a dependency cannot be resolved

Signed-off-by: Jens Reimann <jreimann@redhat.com>

* fix: lookup the top level dependencies from the resolved graph

Instead of using the declared dependency, this change now uses to the
resolved dependency graph to look up the top level dependencies.

The difference is, that a declared dependency may be overridden. And so
the source no longer matches, which results in the dependency missing
from the final SBOM.

Using the resolved dependency graph, overriding dependencies is now
covered as well.

This also aligns with the "all dependencies" approach, which also uses
the resolved dependencies.

Closes: CycloneDX#364
Signed-off-by: Jens Reimann <jreimann@redhat.com>

* refactor: accept suggested changes from review

Signed-off-by: Jens Reimann <jreimann@redhat.com>
Co-authored-by: Amy Keibler <3483663+amy-keibler@users.noreply.github.com>

Signed-off-by: Jens Reimann <jreimann@redhat.com>
Co-authored-by: Amy Keibler <3483663+amy-keibler@users.noreply.github.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
…, component.version is Option in 1.4

Signed-off-by: tokcum <tobias.mucke@gmail.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 18 to 20.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](cachix/install-nix-action@v18...v20)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
- Rust dependencies
  - Swap out "toml" for "toml_edit" due to change with "cargo"
- Update the Nix flake

Signed-off-by: Amelia Keibler <amelia.keibler@gmail.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
Bumps [xml-rs](https://github.com/kornelski/xml-rs) from 0.8.4 to 0.8.10.
- [Changelog](https://github.com/kornelski/xml-rs/blob/main/Changelog.md)
- [Commits](kornelski/xml-rs@0.8.4...0.8.10)

---
updated-dependencies:
- dependency-name: xml-rs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.160 to 1.0.163.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](serde-rs/serde@v1.0.160...v1.0.163)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.70 to 1.0.71.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](dtolnay/anyhow@1.0.70...1.0.71)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
@tokcum tokcum marked this pull request as draft May 18, 2023 19:37
@tokcum tokcum marked this pull request as ready for review May 18, 2023 19:38
@tokcum tokcum marked this pull request as draft May 18, 2023 19:38
@hone
Copy link

hone commented Jul 26, 2023

👋 i'm interested in using SBOMs with some 1.4 features and was curious how things were coming along with 1.4 support/this PR? Are there things others can do to help or just waiting on more reviews?

@ctron
Copy link
Contributor

ctron commented Aug 7, 2023

I would love to understand what the holdup is on this one? If help is needed, I am willing to.

@tokcum
Copy link
Contributor Author

tokcum commented Aug 15, 2023

Hi, this PR has three aspects. First one is obviously to support 1.4. The second one is related to the ability to support more than one version. This involves some refactoring. I tried to keep this to a minimum. The third one is to add tests with real world SBOMs, e.g. from trivy.

I already spent many hours on this and I'm willing to put more effort in. However, so far, I got absolutely no feedback from the maintainer. I didn't expect to get much advice. A confirmation that my approach is okay would suffice, something like "Looks good, go ahead!" or "Sorry, you have to find a different way for ... .".

@stevespringett
Copy link
Member

Hi @tokcum appreciate all the effort that went into this. Would it be possible to resolve the conflicts? I'd like to test locally prior to merging.

@tokcum
Copy link
Contributor Author

tokcum commented Aug 16, 2023

@stevespringett, thank you for your support. Sure, I'll bring this into shape and update the PR. I've some free time at the end of Aug and early in Sept.

Signed-off-by: tokcum <tobias.mucke@gmail.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
Signed-off-by: tokcum <tobias.mucke@gmail.com>
@tokcum
Copy link
Contributor Author

tokcum commented Sep 9, 2023

Hi @stevespringett , it would be great if you find time for a quick review of this draft. I resolved all the conflicts and improved the integration testing differentiating between verification and validation. The approach is described in cyclonedx-bom/tests/README.md.

I will continue to work on the parts of the 1.4 spec not yet supported, see cyclonedx-bom/tests/spec/1.4/not_yet_supported.

Let me know what you think and how to proceed. Thank you.

Signed-off-by: tokcum <tobias.mucke@gmail.com>
@stevespringett
Copy link
Member

FYI @lfrancke

@lfrancke
Copy link
Contributor

Thank you!
I hope that @Shnatsel can help review this once you've marked it as ready for review

Signed-off-by: tokcum <tobias.mucke@gmail.com>
@tokcum tokcum marked this pull request as ready for review September 29, 2023 07:45
@tokcum
Copy link
Contributor Author

tokcum commented Sep 29, 2023

Great! I marked the PR as ready for review. Let me know what to improve.

Signed-off-by: tokcum <tobias.mucke@gmail.com>
@Shnatsel
Copy link
Contributor

I'll take a look and try to get this reviewed on Monday.

Copy link
Contributor

@Shnatsel Shnatsel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some nits like outstanding TODOs and commented out code. I pointed them out where I noticed them. I don't see any major issues.

I appreciate the thorough testing against the spec, and the doc comments!

This is quite a lot of code! In the interest of being able to reproduce this for future versions, how was this file generated? This is a lot of code. Did you use templating of some sort, or is it entirely handwritten?

The big question here is whether we want to keep 1.3 support around, or remove it and go all in on 1.4 (and soon 1.5). I think supporting older versions would create too much of a maintenance burden, and I am not aware of compelling use cases them, so I prefer to gut 1.3 support. Is there a good reason to keep it?

use crate::validation::{
FailureReason, Validate, ValidationContext, ValidationError, ValidationPathComponent,
ValidationResult,
};
use crate::xml::{FromXmlDocument, ToXml};

#[derive(Debug, PartialEq, Eq)]
// todo: derive(Eq)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a TODO? Are there any blockers to actually doing this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there was a detail I was not sure how to handle it appropriately. Spec 1.4 introduced a vulnerability rating with a decimal score. In rust this is a f32 which does not allow to derive Eq.

Today I've worked on a solution. I'm not sure though, that my approach is okay. It would be great if you could check it and share your thoughts. Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the approach you have taken. Thanks!

let bom: crate::specs::v1_3::bom::Bom = self.into();
let bom: crate::specs::v1_3::bom::Bom = self
.try_into()
.expect("todo: error handling in output_as_json_v1_3");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another unfulfilled TODO

Copy link
Contributor Author

@tokcum tokcum Oct 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've looked into this. I had to switch from into() to try_into() because of the different handling of version in Component in 1.3 vs. 1.4. In 1.3 it is mandatory, in 1.4 is is optional. So, a conversion into 1.3 might fail when the Component has no version.
The thing is that try_into returns either Ok(Bom) or Error(BomError), while output_as_json_v1_3() returns a Ok(()) or a Error(JsonWriteError) but not a Error(BomError).

So, how can we handle this. I see three options:

  1. We do not change the interface of output_as_json_v1_3() and if a BomError occurs we write the error as a JSON document.
  2. We do not change the interface of output_as_json_v1_3() and if a BomError occurs we translate the BomError somehow into a JsonWriteError.
  3. We change the interface of output_as_json_v1_3(), which would introduce a breaking change.

We face the same challenge for the output_as_xml_v1_3(). The output_as_*_v1_4() are not affected because there we are able to use into(). However, we could also change it to try_into() to align it.

Are there other options I'm not aware of and which approach should we follow? Please advice. Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are making a field optional to match the spec, which is going to introduce breaking changes no matter if it is pub or not. I think it's a good idea to change the signature to reflect reality while we're making breaking changes anyway.

I've looked at the types and BomError represents the possible failure modes better than a hypothetical modification of JsonWriteError, so let's go with option 3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, great. I'll follow this approach. Thanks.

let bom: crate::specs::v1_3::bom::Bom = self.into();
let bom: crate::specs::v1_3::bom::Bom = self
.try_into()
.expect("todo: error handling in output_as_json_v1_3");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

Comment on lines +337 to +338
// todo: validate vulnerabilities, not sure about which approach to take
// see different approaches for services vs. properties
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another TODO. The validation seems to be implemented for vulnerabilities now, any blockers to actually wiring it up?

}

/// Parse the input as a JSON document conforming to [version 1.4 of the specification](https://cyclonedx.org/docs/1.4/json/)
pub fn parse_from_json_v1_4<R: std::io::Read>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not necessarily an issue with the code, could be my ignorance of the codebase and/or the format, but: is there any reason to keep the 1.3 methods around now that we have 1.4 ones? Isn't 1.4 a strict superset with no new mandatory fields, so that 1.3 data could be parsed by methods expecting 1.4?

Comment on lines +48 to +49
// todo: 1.3 vs. 1.4 - this field is an Option in 1.4
pub version: Option<NormalizedString>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything to be done here? I don't think it should impact this struct.
Maybe validate that this field is present in the 1.3 parser but not 1.4?

Comment on lines +11 to +15
/*
pub(crate) fn try_convert_optional_vec<A, B: TryFrom<A, Error = BomError>>(value: Option<Vec<A>>) -> Option<Result<Vec<B>, BomError>> {
value.map(try_convert_vec)
}
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented out code, here and further down in this file. If it is not used, it should be removed - storing older code is what git is for.

@Shnatsel
Copy link
Contributor

Shnatsel commented Oct 2, 2023

Out of scope of this PR, but a note to self:

We should also exclude the test folder from publishing to crates.io now that it is almost 11MB worth of snapshots, and start tagging releases in the repo (so that Linux distributions could still package it with tests). But our restricted access to the repo may make it harder.

@lfrancke
Copy link
Contributor

lfrancke commented Oct 2, 2023

@Shnatsel See the discussion in the issue #275

I did not fully follow them tbh.
Would the current code (in this PR) allow someone to choose a version of CycloneDX?
As in: I could create an old version if I wanted to?

@Shnatsel
Copy link
Contributor

Shnatsel commented Oct 2, 2023

Yes.

In fact, this PR does not convert cargo cyclonedx to generate 1.4, it only changes the cyclonedx-bom crate, and cargo cyclonedx keeps generating v1.3.

@lfrancke
Copy link
Contributor

lfrancke commented Oct 3, 2023

Thanks. I'll do a survey on the CycloneDX slack what other libraries do.

There's arguments for both sides
Some downstream tools might expect an older version and can't deal with newer versions.

Oh the other hand the versions did avoid breaking changes so far I believe.

@lfrancke
Copy link
Contributor

lfrancke commented Oct 4, 2023

There is a good discussion about this in Slack now: https://cyclonedx.slack.com/archives/CV062H2GH/p1696319537238529

Summary:

  • cdxgen supports 1.4 and 1.5
  • PHP, JS, Python support multiple CDX versions
  • Java has a dedicated major version per CDX schema version
  • There are some differences between XML and JSON
  • Adoption of 1.5 is not widespread yet so if we support 1.5 we should keep on supporting 1.4 as well
  • Suggestion: Model for latest spec and then "downgrade" previous to serialization (Go and .NET do it like that - https://github.com/CycloneDX/cyclonedx-go/blob/master/convert.go )

What does it mean for us?
It'd be great if we could support older versions of CycloneDX but I - personally - would be fine with 1.4 as the minimum if it makes this PR easier. If we already have all the logic in place to keep 1.3 as well and you both think it's fine then I'm happy to keep it in as well.

I'd rather have a version of this library with 1.4 support this month than a version that supports multiple CycloneDX specs in three months.

@Shnatsel
Copy link
Contributor

Shnatsel commented Oct 4, 2023

The summary of the technical considerations is:

  • Every 1.3 document is a valid 1.5 document. This is an explicit design foal of the CycloneDX format.
  • Not every 1.5 document is a valid 1.3 document. For example, some fields were made optional going from 1.3 to 1.4.

So there are use cases for writing in older format versions, to be compatible with older software. But not so much for reading from older format versions, since any 1.3 document can be loaded by a 1.5 parser.


## Sign off your commits

Please sign off your commits,
Copy link
Member

@jkowalleck jkowalleck Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated text snippet:

Please sign off your commits, to show that you agree to publish your changes under the current terms and licenses of the project
, and to indicate agreement with [Developer Certificate of Origin (DCO)](https://developercertificate.org/).

@jkowalleck
Copy link
Member

Every 1.3 document is a valid 1.5 document. This is an explicit design foal of the CycloneDX format.

there are exceptions, especially bug fixes.

For example CycloneDX/specification#204: there was a bug in the CycloneDX json schema < 1.5 which allowed a mix of multiple licenses.

  • This bug was fixed in CycloneDX 1.5
  • This bug did not exist in CycloneDX xml schema < 1.5. The correct behaviour is: licenses are either a list of exactly one SPDX Expression or a list of unbound amount of (either named license or SPDX ID).

Since this wrong behavior was allowed in CDX 1.3 json, there might exist documents that are not valid CDX 1.5 json - for obvious reasons.

@tokcum tokcum changed the title Add support for 1.4 Add support for 1.4 to cyclonedx-bom Oct 4, 2023
@tokcum
Copy link
Contributor Author

tokcum commented Oct 4, 2023

There are some nits like outstanding TODOs and commented out code. I pointed them out where I noticed them. I don't see any major issues.

I appreciate the thorough testing against the spec, and the doc comments!

This is quite a lot of code! In the interest of being able to reproduce this for future versions, how was this file generated? This is a lot of code. Did you use templating of some sort, or is it entirely handwritten?

The big question here is whether we want to keep 1.3 support around, or remove it and go all in on 1.4 (and soon 1.5). I think supporting older versions would create too much of a maintenance burden, and I am not aware of compelling use cases them, so I prefer to gut 1.3 support. Is there a good reason to keep it?

Hi @Shnatsel,

thank you for the feedback. Great, that we get this going. I'll continue to work on it.

To answer your questions, when I started to work on this I started by comparing the official specs of 1.3 vs. 1.4 to find out the differences. The result was that there were only a few really new things to consider. Most was "more of the same". Then I discussed the approach with @amy-keibler. She told my to keep the existing model part untouched as far as possible and add the code to support 1.4 under specs.
My understanding from what Amy told me was, that cyclonedx-bom should be able to support multiple versions with a best effort approach, i.e. not getting to strict about the details. Here is her comment that made me think in this direction.

Having said that, I copied specs/1.3 to specs/1.4 and started by implementing the "new" things in the official 1.4 spec. As I said, from an implementation perspective, most was not really new just more of the same. To be honest, at that time, many things were new to me, such as snapshot testing and how the existing code handled serialization and deserialization of XML. No templating behind the scenes, just a thoroughly done copy and paste job. Not very smart, sorry. Maybe we have to overhaul the approach at some point in the future.

To answer your last question, the reason why I started to work on this is, I wanted to be able to deserialize SBOMs generated by trivy. At that time, trivy generated SBOMs in CycloneDX version 1.4. Meanwhile trivy uses 1.5. Anyway, I'm in charge of security at a software editor with many formerly created SBOMs which I would like to process with cyclonedx-bom. So, it would be great if cyclonedx-bom could support former versions as well.

@tokcum
Copy link
Contributor Author

tokcum commented Oct 4, 2023

It'd be great if we could support older versions of CycloneDX but I - personally - would be fine with 1.4 as the minimum if it makes this PR easier. If we already have all the logic in place to keep 1.3 as well and you both think it's fine then I'm happy to keep it in as well.

I reviewed my conversion with Amy and think that we should try to support multiple versions. nscur0 also mentioned the approach in the Slack discussion.

I'm pretty confident we can make it and I think we should consider to add some tests to check the compatibility when serializing / de-serializing different versions.

@lfrancke
Copy link
Contributor

lfrancke commented Oct 4, 2023

Thanks!
If we can support multiple versions then I'm very happy. I'll leave the implementation discussions to you but am happy to merge as soon as @Shnatsel approves.

Signed-off-by: tokcum <tobias.mucke@gmail.com>
@lfrancke
Copy link
Contributor

Just a heads-up that this now has a few merge conflicts so you'd need to rebase it.

@Shnatsel
Copy link
Contributor

Shnatsel commented Nov 3, 2023

FYI we'd like to get this merged in the next week or so, and then start on implementing v1.5. Please let me know if I can help in any way.

@lfrancke
Copy link
Contributor

lfrancke commented Nov 8, 2023

Hi @tokcum, if we don't hear back from you we plan on taking this PR over the finish line at the beginning of next week.

Thank you for all your work so far and being so patient.

@tokcum
Copy link
Contributor Author

tokcum commented Nov 8, 2023

@Shnatsel and @lfrancke

Sorry, I've been occupied by other projects during the last weeks. I've remotely followed all the other PRs you got done recently and I would be very happy to see this PR finished by you. Thanks.

///
/// Defined via the [XML schema](https://cyclonedx.org/docs/1.4/xml/#type_ratingType)
#[derive(Debug, PartialEq, Eq)]
pub struct Score(i32);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a small crate called ordered-float that should cover this nicely. It provides implementations for Ord & Eq. The Score struct then can look like:

#[derive(Debug, PartialEq, Eq)]
pub struct Score(OrderedFloat<f32>);

impl Score {
    pub fn new_unchecked(score: f32) -> Self {
        Score(score.into())
    }

    pub fn from_f32(score: f32) -> Option<Self> {
        // or add range check here, maybe clamp between 0..=10?
        Some(Score(score.into()))
    }

    pub fn to_f32(&self) -> f32 {
        self.0.0
    }
}

impl From<f32> for Score {
    fn from(value: f32) -> Self {
        Score(value.into())
    }
}

impl From<Score> for f32 {
    fn from(value: Score) -> f32 {
        value.0.0
    }
}

This simplifies conversions & should provide easier handling of scores.

@ctron
Copy link
Contributor

ctron commented Nov 28, 2023

It would be great to see that merged. Is there any case I could help?

@lfrancke
Copy link
Contributor

Someone from the team will pick this up soon (this week).

There are just a few minor todos to handle and we believe the Snapshot tests don't necessarily pull their weight.
They are pretty big so our current plan is to prune them from the git history and do a followup PR.

I hope to see some progress here this or next week.

@Shnatsel
Copy link
Contributor

Shnatsel commented Dec 6, 2023

The PR based on this one has been merged: #575

I am going to go ahead and close this. Thanks for your contribution @tokcum !

@Shnatsel Shnatsel closed this Dec 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants