From 024a5a9f3fa94c11f4c485077fcd1b2c106a8aed Mon Sep 17 00:00:00 2001 From: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Date: Tue, 25 Jan 2022 15:34:16 -0500 Subject: [PATCH] Add dependencies to cyclonedx (#768) Add dependencies to cyclonedx Signed-off-by: Christopher Phillips Co-authored-by: hectorj2f --- .../formats/common/cyclonedxhelpers/format.go | 60 ++++++++++++++++++- .../formats/cyclonedx13json/encoder_test.go | 3 + .../formats/cyclonedx13xml/encoder_test.go | 8 +++ syft/artifact/relationship.go | 12 ++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/internal/formats/common/cyclonedxhelpers/format.go b/internal/formats/common/cyclonedxhelpers/format.go index b472556be..132dcd530 100644 --- a/internal/formats/common/cyclonedxhelpers/format.go +++ b/internal/formats/common/cyclonedxhelpers/format.go @@ -5,7 +5,9 @@ import ( "github.com/CycloneDX/cyclonedx-go" "github.com/anchore/syft/internal" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/version" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" @@ -29,6 +31,12 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM { } components = append(components, toOSComponent(s.Artifacts.LinuxDistribution)...) cdxBOM.Components = &components + + dependencies := toDependencies(s.Relationships) + if len(dependencies) > 0 { + cdxBOM.Dependencies = &dependencies + } + return cdxBOM } @@ -97,18 +105,66 @@ func toBomDescriptor(name, version string, srcMetadata source.Metadata) *cyclone } } +// used to indicate that a relationship listed under the syft artifact package can be represented as a cyclonedx dependency. +// NOTE: CycloneDX provides the ability to describe components and their dependency on other components. +// The dependency graph is capable of representing both direct and transitive relationships. +// If a relationship is either direct or transitive it can be included in this function. +// An example of a relationship to not include would be: OwnershipByFileOverlapRelationship. +func isExpressiblePackageRelationship(ty artifact.RelationshipType) bool { + switch ty { + case artifact.RuntimeDependencyOfRelationship: + return true + case artifact.DevDependencyOfRelationship: + return true + case artifact.BuildDependencyOfRelationship: + return true + case artifact.DependencyOfRelationship: + return true + } + return false +} + +func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependency { + result := make([]cyclonedx.Dependency, 0) + for _, r := range relationships { + exists := isExpressiblePackageRelationship(r.Type) + if !exists { + log.Warnf("unable to convert relationship from CycloneDX 1.3 JSON, dropping: %+v", r) + continue + } + + innerDeps := []cyclonedx.Dependency{} + innerDeps = append(innerDeps, cyclonedx.Dependency{Ref: string(r.From.ID())}) + result = append(result, cyclonedx.Dependency{ + Ref: string(r.To.ID()), + Dependencies: &innerDeps, + }) + } + return result +} + func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component { switch srcMetadata.Scheme { case source.ImageScheme: + bomRef, err := artifact.IDByHash(srcMetadata.ImageMetadata.ID) + if err != nil { + log.Warnf("unable to get fingerprint of image metadata=%s: %+v", srcMetadata.ImageMetadata.ID, err) + } return &cyclonedx.Component{ + BOMRef: string(bomRef), Type: cyclonedx.ComponentTypeContainer, Name: srcMetadata.ImageMetadata.UserInput, Version: srcMetadata.ImageMetadata.ManifestDigest, } case source.DirectoryScheme, source.FileScheme: + bomRef, err := artifact.IDByHash(srcMetadata.Path) + if err != nil { + log.Warnf("unable to get fingerprint of source metadata path=%s: %+v", srcMetadata.Path, err) + } return &cyclonedx.Component{ - Type: cyclonedx.ComponentTypeFile, - Name: srcMetadata.Path, + BOMRef: string(bomRef), + Type: cyclonedx.ComponentTypeFile, + Name: srcMetadata.Path, } } diff --git a/internal/formats/cyclonedx13json/encoder_test.go b/internal/formats/cyclonedx13json/encoder_test.go index 22361e889..f971a5b07 100644 --- a/internal/formats/cyclonedx13json/encoder_test.go +++ b/internal/formats/cyclonedx13json/encoder_test.go @@ -37,5 +37,8 @@ func cycloneDxRedactor(s []byte) []byte { for _, pattern := range []*regexp.Regexp{serialPattern, rfc3339Pattern, sha256Pattern} { s = pattern.ReplaceAll(s, []byte("redacted")) } + // the bom-ref will be autogenerated every time, the value here should not be directly tested in snapshot tests + s = regexp.MustCompile(` "bom-ref": .*\n`).ReplaceAll(s, []byte("")) + return s } diff --git a/internal/formats/cyclonedx13xml/encoder_test.go b/internal/formats/cyclonedx13xml/encoder_test.go index 6c34c0db6..b2c68701b 100644 --- a/internal/formats/cyclonedx13xml/encoder_test.go +++ b/internal/formats/cyclonedx13xml/encoder_test.go @@ -38,5 +38,13 @@ func cycloneDxRedactor(s []byte) []byte { for _, pattern := range []*regexp.Regexp{serialPattern, rfc3339Pattern, sha256Pattern} { s = pattern.ReplaceAll(s, []byte("redacted")) } + + // the bom-ref will be autogenerated every time, the value here should not be directly tested in snapshot tests + bomRefPattern := regexp.MustCompile(` bom-ref="[a-zA-Z0-9\-:]+"`) + bomRef3339Pattern := regexp.MustCompile(`([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))`) + for _, pattern := range []*regexp.Regexp{bomRefPattern, bomRef3339Pattern} { + s = pattern.ReplaceAll(s, []byte("")) + } + return s } diff --git a/syft/artifact/relationship.go b/syft/artifact/relationship.go index 921e475a1..4c2fadfab 100644 --- a/syft/artifact/relationship.go +++ b/syft/artifact/relationship.go @@ -9,6 +9,18 @@ const ( // ContainsRelationship (supports any-to-any linkages) is a proxy for the SPDX 2.2 CONTAINS relationship. ContainsRelationship RelationshipType = "contains" + + // RuntimeDependencyOfRelationship is a proxy for the SPDX 2.2.1 RUNTIME_DEPENDENCY_OF relationship. + RuntimeDependencyOfRelationship RelationshipType = "runtime-dependency-of" + + // DevDependencyOfRelationship is a proxy for the SPDX 2.2.1 DEV_DEPENDENCY_OF relationship. + DevDependencyOfRelationship RelationshipType = "dev-dependency-of" + + // BuildDependencyOfRelationship is a proxy for the SPDX 2.2.1 BUILD_DEPENDENCY_OF relationship. + BuildDependencyOfRelationship RelationshipType = "build-dependency-of" + + // DependencyOfRelationship is a proxy for the SPDX 2.2.1 DEPENDENCY_OF relationship. + DependencyOfRelationship RelationshipType = "dependency-of" ) type RelationshipType string