From e547686a5ed7d0018f1f70da097ae100b1872c3a Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Tue, 9 Jul 2024 14:30:37 +0530 Subject: [PATCH 01/19] add ntia compliance readme report Signed-off-by: Vivek Kumar Sahu --- Compliance.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Compliance.md b/Compliance.md index 23c4310..e1e219b 100644 --- a/Compliance.md +++ b/Compliance.md @@ -66,3 +66,25 @@ The [OpenChain Telco](https://github.com/OpenChain-Project/Reference-Material/bl | Timing of SBOM delivery | 3.6 | `SBOM delivery time` | delivery time | | | Method of SBOM delivery | 3.7 | `SBOM delivery method` | delivery method | | | SBOM Scope | 3.8 | `SBOM scope` | sbom scope | | + + +## NTIA minimum elements: SBOM Requirements for NTIA + +The [NTIA](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2.pdf) specifies mandatory properties for an SBOM. Below is how we have derived all the values. + +| NTIA | NTIA Data field | CycloneDx | SPDX(2.3) | Notes | +| :--- | :--- | :--- | :--- | :--- | +|1. SBOM formats| `specification` | BomFormat | SPDXversion | CycloneDX and SPDX only | +|| `specification version` | SpecVersion | SPDXversion | CycloneDX 1.4 and above, SPDX 2.3 and above | +|2 Recommended| `Build SBOM` | metadata->lifecycles (1.5 and above) | no-deterministic-field | | +|| `Depth` | dependencies, compositions | relationships | A complex topic, mostly resolved via attestations via compositions, but spdx lacks that field now| +|| `license`| component->license| packageConcluded, packageDeclated| we lookup sdpx,spdx-exceptions,aboutcode, and licenseRef-| +|| `hash` | component->hashes | package->checksums | we only look for sha-256| +|3 Required SBOM fields| `SBOM authors` | metadata->authors, metadata->supplier | creator | We are primarily looking for email or url from these fields, if the name exists but email/url missing its deemed non-compliant| +| | | metadata->manufacturer | | | +|| `SBOM timestamp`| metadata->timestamp| created | | +|3 Required Component fields| `creator` | component->supplier | packageSupplier, packageOriginator | Looking for email or url, for spdx, we check supplier then originatior(manufacturer)| +|| `name` | component->name| package->name| | +|| `version` | component->version| package->version| | +|| `dependencies` | dependencies, compositions| relationships| cdx we look for attestations via compositions, spdx nothing exists| +| | `other uniq identifiers`| component->cpe, component->purl| package->externalReference->security (cpe/purl) | | From c92ea61b20476bb174ec0f6f4bfcf8256b2184f3 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Tue, 9 Jul 2024 16:44:22 +0530 Subject: [PATCH 02/19] correct numbering Signed-off-by: Vivek Kumar Sahu --- Compliance.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Compliance.md b/Compliance.md index e1e219b..e1ffcd4 100644 --- a/Compliance.md +++ b/Compliance.md @@ -76,14 +76,14 @@ The [NTIA](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Tech | :--- | :--- | :--- | :--- | :--- | |1. SBOM formats| `specification` | BomFormat | SPDXversion | CycloneDX and SPDX only | || `specification version` | SpecVersion | SPDXversion | CycloneDX 1.4 and above, SPDX 2.3 and above | -|2 Recommended| `Build SBOM` | metadata->lifecycles (1.5 and above) | no-deterministic-field | | +|2. Recommended elements| `Build SBOM` | metadata->lifecycles (1.5 and above) | no-deterministic-field | | || `Depth` | dependencies, compositions | relationships | A complex topic, mostly resolved via attestations via compositions, but spdx lacks that field now| || `license`| component->license| packageConcluded, packageDeclated| we lookup sdpx,spdx-exceptions,aboutcode, and licenseRef-| || `hash` | component->hashes | package->checksums | we only look for sha-256| -|3 Required SBOM fields| `SBOM authors` | metadata->authors, metadata->supplier | creator | We are primarily looking for email or url from these fields, if the name exists but email/url missing its deemed non-compliant| +|3. Required SBOM fields| `SBOM authors` | metadata->authors, metadata->supplier | creator | We are primarily looking for email or url from these fields, if the name exists but email/url missing its deemed non-compliant| | | | metadata->manufacturer | | | || `SBOM timestamp`| metadata->timestamp| created | | -|3 Required Component fields| `creator` | component->supplier | packageSupplier, packageOriginator | Looking for email or url, for spdx, we check supplier then originatior(manufacturer)| +|4. Required Component fields| `creator` | component->supplier | packageSupplier, packageOriginator | Looking for email or url, for spdx, we check supplier then originatior(manufacturer)| || `name` | component->name| package->name| | || `version` | component->version| package->version| | || `dependencies` | dependencies, compositions| relationships| cdx we look for attestations via compositions, spdx nothing exists| From dde793238b613f7e6f0954dee01b0fd69f2ceaae Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Tue, 9 Jul 2024 19:33:32 +0530 Subject: [PATCH 03/19] added a ntia result Signed-off-by: Vivek Kumar Sahu rename relation interface Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/ntia.go | 307 +++++++++++++++++++++++++++++++++++++++++ pkg/sbom/cdx.go | 16 +-- pkg/sbom/document.go | 2 +- pkg/sbom/relation.go | 20 +-- pkg/sbom/spdx.go | 16 +-- 5 files changed, 334 insertions(+), 27 deletions(-) diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index acf4759..d9cec06 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -16,9 +16,12 @@ package compliance import ( "context" + "strings" + "time" "github.com/interlynk-io/sbomqs/pkg/logger" "github.com/interlynk-io/sbomqs/pkg/sbom" + "github.com/samber/lo" ) func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) *db { @@ -27,6 +30,310 @@ func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outForm db := newDB() + db.addRecord(ntiaSpec(doc)) + db.addRecord(ntiaSpecVersion(doc)) + db.addRecord(ntiaSbomCreator(doc)) + db.addRecord(ntiaSbomCreatedTimestamp(doc)) + db.addRecords(ntiaComponents(doc)) + db.addRecord(ntiaBuildPhase(doc)) + db.addRecord(ntiaSbomDepth(doc)) + db.addRecord(ntiaComponentHash(doc)) + db.addRecord(ntiaComponentlicense(doc)) + return db +} + +// format +func ntiaSpec(doc sbom.Document) *record { + v := doc.Spec().GetSpecType() + v_to_lower := strings.Trim(strings.ToLower(v), " ") + result := "" + score := 0.0 + + if v_to_lower == "spdx" { + result = v + score = 10.0 + } else if v_to_lower == "cyclonedx" { + result = v + score = 10.0 + } + return newRecordStmt(SBOM_SPEC, "SBOM format", result, score) +} + +func ntiaSpecVersion(doc sbom.Document) *record { + spec := doc.Spec().GetSpecType() + version := doc.Spec().GetVersion() + + result, score := "", 0.0 + + if spec == "spdx" { + count := lo.Count(valid_cra_spdx_versions, version) + if count > 0 { + result = version + score = 10.0 + } + } else if spec == "cyclonedx" { + count := lo.Count(valid_cra_cdx_versions, version) + if count > 0 { + result = version + score = 10.0 + } + } + return newRecordStmt(SBOM_SPEC_VERSION, "SBOM format", result, score) +} + +// Required Sbom stuffs +func ntiaSbomCreator(doc sbom.Document) *record { + spec := doc.Spec().GetSpecType() + + if spec == "spdx" { + result := "" + score := 0.0 + name := "" + if tools := doc.Tools(); tools != nil { + for _, tool := range tools { + if name = tool.GetName(); name != "" { + result = name + score = 10.0 + break + } + } + } + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + + } else if spec == "cyclonedx" { + result := "" + score := 0.0 + + for _, author := range doc.Authors() { + if author.Email() != "" { + result = author.Email() + score = 10.0 + break + } + } + + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + } + + supplier := doc.Supplier() + + if supplier != nil { + if supplier.GetEmail() != "" { + result = supplier.GetEmail() + score = 10.0 + } + + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + } + + if supplier.GetUrl() != "" { + result = supplier.GetUrl() + score = 10.0 + } + + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + } + + if supplier.GetContacts() != nil { + for _, contact := range supplier.GetContacts() { + if contact.Email() != "" { + result = contact.Email() + score = 10.0 + break + } + } + + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + } + } + } + + manufacturer := doc.Manufacturer() + + if manufacturer != nil { + if manufacturer.Email() != "" { + result = manufacturer.Email() + score = 10.0 + } + + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + } + + if manufacturer.Url() != "" { + result = manufacturer.Url() + score = 10.0 + } + + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + } + + if manufacturer.Contacts() != nil { + for _, contact := range manufacturer.Contacts() { + if contact.Email() != "" { + result = contact.Email() + score = 10.0 + break + } + } + + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + } + } + } + } + return newRecordStmt(SBOM_CREATOR, "Required SBOM fields", "", 0.0) +} + +func ntiaSbomCreatedTimestamp(doc sbom.Document) *record { + score := 0.0 + result := doc.Spec().GetCreationTimestamp() + if result != "" { + _, err := time.Parse(time.RFC3339, result) + if err != nil { + score = 0.0 + } else { + score = 10.0 + } + } + return newRecordStmt(SBOM_TIMESTAMP, "Required SBOM fields", result, score) } + +// Required component stuffs +func ntiaComponents(doc sbom.Document) []*record {} + +func ntiaComponentName(component sbom.GetComponent) *record { + if result := component.GetName(); result != "" { + return newRecordStmt(COMP_NAME, component.GetID(), result, 10.0) + } + return newRecordStmt(COMP_NAME, component.GetID(), "", 0.0) +} + +func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *record { + spec := doc.Spec().GetSpecType() + + if spec == "spdx" { + if supplier := component.Suppliers().GetEmail(); supplier != "" { + return newRecordStmt(PACK_SUPPLIER, component.GetID(), supplier, 10.0) + } + return newRecordStmt(PACK_SUPPLIER, component.GetID(), "", 0.0) + } else if spec == "cyclonedx" { + result := "" + score := 0.0 + + supplier := component.Suppliers() + if supplier != nil { + if supplier.GetEmail() != "" { + result = supplier.GetEmail() + score = 10.0 + } + + if result != "" { + return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + } + + if supplier.GetUrl() != "" { + result = supplier.GetUrl() + score = 10.0 + } + + if result != "" { + return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + } + + if supplier.GetContacts() != nil { + for _, contact := range supplier.GetContacts() { + if contact.Email() != "" { + result = contact.Email() + score = 10.0 + break + } + } + + if result != "" { + return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + } + } + } + + manufacturer := component.Manufacturer() + + if manufacturer != nil { + if manufacturer.Email() != "" { + result = manufacturer.Email() + score = 10.0 + } + + if result != "" { + return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + } + + if manufacturer.Url() != "" { + result = manufacturer.Url() + score = 10.0 + } + + if result != "" { + return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + } + + if manufacturer.Contacts() != nil { + for _, contact := range manufacturer.Contacts() { + if contact.Email() != "" { + result = contact.Email() + score = 10.0 + break + } + } + + if result != "" { + return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + } + } + } + + } + return newRecordStmt(COMP_CREATOR, component.GetID(), "", 0.0) +} + +func ntiaComponentVersion(component sbom.GetComponent) *record { + result := component.GetVersion() + + if result != "" { + return newRecordStmt(COMP_VERSION, component.GetID(), result, 10.0) + } + + return newRecordStmt(COMP_VERSION, component.GetID(), "", 0.0) +} +func ntiaComponent(doc sbom.Document) *record {} +func ntiaComponentDepth(doc sbom.Document) *record {} +func ntiaComponentOtherUniqIds(doc sbom.Document) *record {} + +// Recommended sbom stuffs +// lifecycle +func ntiaBuildPhase(doc sbom.Document) *record { + lifecycles := doc.Lifecycles() + result, score := "", 0.0 + + found := lo.Count(lifecycles, "build") + + if found > 0 { + result = "build" + score = 10.0 + } + + return newRecordStmt(SBOM_BUILD, "doc", result, score) +} +func ntiaSbomDepth(doc sbom.Document) *record {} + +// Recommended component stuffs +func ntiaComponentHash(doc sbom.Document) *record {} +func ntiaComponentlicense(doc sbom.Document) *record {} diff --git a/pkg/sbom/cdx.go b/pkg/sbom/cdx.go index 6408cea..1fcecd5 100644 --- a/pkg/sbom/cdx.go +++ b/pkg/sbom/cdx.go @@ -42,7 +42,7 @@ type cdxDoc struct { comps []GetComponent authors []Author tools []GetTool - rels []Relation + rels []GetRelation logs []string primaryComponent bool lifecycles []string @@ -101,7 +101,7 @@ func (c cdxDoc) Tools() []GetTool { return c.tools } -func (c cdxDoc) Relations() []Relation { +func (c cdxDoc) Relations() []GetRelation { return c.rels } @@ -285,9 +285,9 @@ func copyC(cdxc *cydx.Component, c *cdxDoc) *Component { nc.isPrimary = true } - fromRelsPresent := func(rels []Relation, compID string) bool { + fromRelsPresent := func(rels []GetRelation, compID string) bool { for _, r := range rels { - if r.From() == compID { + if r.GetFrom() == compID { return true } } @@ -534,13 +534,13 @@ func (c *cdxDoc) parseManufacturer() { } func (c *cdxDoc) parseRels() { - c.rels = []Relation{} + c.rels = []GetRelation{} for _, r := range lo.FromPtr(c.doc.Dependencies) { for _, d := range lo.FromPtr(r.Dependencies) { - nr := relation{} - nr.from = r.Ref - nr.to = d + nr := Relation{} + nr.From = r.Ref + nr.To = d c.rels = append(c.rels, nr) } } diff --git a/pkg/sbom/document.go b/pkg/sbom/document.go index 3cca7ca..c3fb027 100644 --- a/pkg/sbom/document.go +++ b/pkg/sbom/document.go @@ -20,7 +20,7 @@ package sbom type Document interface { Spec() Spec Components() []GetComponent - Relations() []Relation + Relations() []GetRelation Authors() []Author Tools() []GetTool Logs() []string diff --git a/pkg/sbom/relation.go b/pkg/sbom/relation.go index c756619..87dbaa1 100644 --- a/pkg/sbom/relation.go +++ b/pkg/sbom/relation.go @@ -15,20 +15,20 @@ package sbom //counterfeiter:generate . Relation -type Relation interface { - From() string - To() string +type GetRelation interface { + GetFrom() string + GetTo() string } -type relation struct { - from string - to string +type Relation struct { + From string + To string } -func (r relation) From() string { - return r.from +func (r Relation) GetFrom() string { + return r.From } -func (r relation) To() string { - return r.to +func (r Relation) GetTo() string { + return r.To } diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index 502bc53..66ab4fc 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -49,7 +49,7 @@ type SpdxDoc struct { Comps []GetComponent authors []Author SpdxTools []GetTool - rels []Relation + rels []GetRelation logs []string primaryComponent bool primaryComponentId string @@ -109,7 +109,7 @@ func (s SpdxDoc) Tools() []GetTool { return s.SpdxTools } -func (s SpdxDoc) Relations() []Relation { +func (s SpdxDoc) Relations() []GetRelation { return s.rels } @@ -229,9 +229,9 @@ func (s *SpdxDoc) parseComps() { nc.isPrimary = s.primaryComponentId == string(sc.PackageSPDXIdentifier) - fromRelsPresent := func(rels []Relation, id string) bool { + fromRelsPresent := func(rels []GetRelation, id string) bool { for _, r := range rels { - if r.From() == id { + if r.GetFrom() == id { return true } } @@ -270,13 +270,13 @@ func (s *SpdxDoc) parseAuthors() { } func (s *SpdxDoc) parseRels() { - s.rels = []Relation{} + s.rels = []GetRelation{} var err error var aBytes, bBytes []byte for _, r := range s.doc.Relationships { - nr := relation{} + nr := Relation{} switch strings.ToUpper(r.Relationship) { case spdx_common.TypeRelationshipDescribe: fallthrough @@ -293,8 +293,8 @@ func (s *SpdxDoc) parseRels() { continue } - nr.from = string(aBytes) - nr.to = string(bBytes) + nr.From = string(aBytes) + nr.To = string(bBytes) s.rels = append(s.rels, nr) } } From 9d1d507684d4f5c8592ad7a08c08987c864da5be Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Thu, 11 Jul 2024 14:35:28 +0530 Subject: [PATCH 04/19] add ntia compliance command Signed-off-by: Vivek Kumar Sahu --- cmd/compliance.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/compliance.go b/cmd/compliance.go index 1307172..320a869 100644 --- a/cmd/compliance.go +++ b/cmd/compliance.go @@ -57,7 +57,7 @@ func setupEngineParams(cmd *cobra.Command, args []string) *engine.Params { engParams.Detailed, _ = cmd.Flags().GetBool("detailed") engParams.Json, _ = cmd.Flags().GetBool("json") - // engParams.Ntia, _ = cmd.Flags().GetBool("ntia") + engParams.Ntia, _ = cmd.Flags().GetBool("ntia") engParams.Cra, _ = cmd.Flags().GetBool("cra") engParams.Oct, _ = cmd.Flags().GetBool("oct") @@ -82,7 +82,7 @@ func init() { complianceCmd.MarkFlagsMutuallyExclusive("json", "basic", "detailed") // Standards control - // complianceCmd.Flags().BoolP("ntia", "n", false, "check for NTIA minimum elements compliance") + complianceCmd.Flags().BoolP("ntia", "n", false, "check for NTIA minimum elements compliance") complianceCmd.Flags().BoolP("bsi", "c", false, "BSI TR-03183-2 v1.1 compliance") // complianceCmd.MarkFlagsMutuallyExclusive("ntia", "cra") complianceCmd.Flags().BoolP("oct", "t", false, "OpenChainTelco compliance") From 73a9d3f26ccd400a13b19ef1d4326a4d52ac46cc Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Thu, 11 Jul 2024 14:36:00 +0530 Subject: [PATCH 05/19] add ntia compliance core functionality Signed-off-by: Vivek Kumar Sahu remove duplicate Signed-off-by: Vivek Kumar Sahu increase the visiblity of variables, interfaces, struct for reuse Signed-off-by: Vivek Kumar Sahu add pass test for ntia cdx as well as spdx part Signed-off-by: Vivek Kumar Sahu update readme ntia compliance for remaining ones Signed-off-by: Vivek Kumar Sahu fix alligment Signed-off-by: Vivek Kumar Sahu re-update readme Signed-off-by: Vivek Kumar Sahu re structure fields to ntia complaince report Signed-off-by: Vivek Kumar Sahu --- Compliance.md | 29 +- pkg/compliance/cra.go | 8 +- pkg/compliance/ntia.go | 215 ++++++++----- pkg/compliance/ntia_report.go | 140 +++++++++ pkg/compliance/ntia_score.go | 128 ++++++++ pkg/compliance/ntia_test.go | 562 ++++++++++++++++++++++++++++++++++ pkg/sbom/author.go | 29 +- pkg/sbom/cdx.go | 108 +++---- pkg/sbom/component.go | 20 +- pkg/sbom/component_test.go | 12 +- pkg/sbom/document.go | 2 +- pkg/sbom/spdx.go | 30 +- pkg/scorer/quality.go | 4 +- 13 files changed, 1092 insertions(+), 195 deletions(-) create mode 100644 pkg/compliance/ntia_report.go create mode 100644 pkg/compliance/ntia_score.go create mode 100644 pkg/compliance/ntia_test.go diff --git a/Compliance.md b/Compliance.md index e1ffcd4..fcaf4b3 100644 --- a/Compliance.md +++ b/Compliance.md @@ -72,19 +72,16 @@ The [OpenChain Telco](https://github.com/OpenChain-Project/Reference-Material/bl The [NTIA](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2.pdf) specifies mandatory properties for an SBOM. Below is how we have derived all the values. -| NTIA | NTIA Data field | CycloneDx | SPDX(2.3) | Notes | -| :--- | :--- | :--- | :--- | :--- | -|1. SBOM formats| `specification` | BomFormat | SPDXversion | CycloneDX and SPDX only | -|| `specification version` | SpecVersion | SPDXversion | CycloneDX 1.4 and above, SPDX 2.3 and above | -|2. Recommended elements| `Build SBOM` | metadata->lifecycles (1.5 and above) | no-deterministic-field | | -|| `Depth` | dependencies, compositions | relationships | A complex topic, mostly resolved via attestations via compositions, but spdx lacks that field now| -|| `license`| component->license| packageConcluded, packageDeclated| we lookup sdpx,spdx-exceptions,aboutcode, and licenseRef-| -|| `hash` | component->hashes | package->checksums | we only look for sha-256| -|3. Required SBOM fields| `SBOM authors` | metadata->authors, metadata->supplier | creator | We are primarily looking for email or url from these fields, if the name exists but email/url missing its deemed non-compliant| -| | | metadata->manufacturer | | | -|| `SBOM timestamp`| metadata->timestamp| created | | -|4. Required Component fields| `creator` | component->supplier | packageSupplier, packageOriginator | Looking for email or url, for spdx, we check supplier then originatior(manufacturer)| -|| `name` | component->name| package->name| | -|| `version` | component->version| package->version| | -|| `dependencies` | dependencies, compositions| relationships| cdx we look for attestations via compositions, spdx nothing exists| -| | `other uniq identifiers`| component->cpe, component->purl| package->externalReference->security (cpe/purl) | | +| NTIA minimum elements | Section ID | NTIA Fields | CycloneDX |SPDX(2.3) | Notes | +| :--- | :--- |:--- | :--- | :--- | :--- | +| Data Fields | 1.1 | Author of the SBOM data | metadata->authors, metadata->supplier | creator | | +| | 1.2 | Timestamp | metadata->timestamp | created | | +| | 1.3 | Dependency Relationship | dependencies, composition | relationships | | +| | 1.4 | present | | | all package elements | +| | 1.5 | Component Name | component->name | package->name | | +| | 1.6 | Supplier Name | component->supplier | packageSupplier, packageOriginator | | +| | 1.7 | Version of Component | component->version | package->version | | +| | 1.8 | Other Uniq IDs | component->cpe, component->purl | DocumentNamespace, SPDXID | | +| Automation Support | 2.1 | Format | BomFormat | SPDXversion | | +| Practices and Processes | 3.1 | Depth | dependencies, compositions | relationships | | +| | 3.2 | Known Unknowns | | | | diff --git a/pkg/compliance/cra.go b/pkg/compliance/cra.go index 5b7b60f..c33aaba 100644 --- a/pkg/compliance/cra.go +++ b/pkg/compliance/cra.go @@ -191,8 +191,8 @@ func craCreator(doc sbom.Document) *record { score := 0.0 for _, author := range doc.Authors() { - if author.Email() != "" { - result = author.Email() + if author.GetEmail() != "" { + result = author.GetEmail() score = 10.0 break } @@ -398,7 +398,7 @@ func craComponentOtherUniqIds(component sbom.GetComponent) *record { result := "" score := 0.0 - purl := component.Purls() + purl := component.GetPurls() if len(purl) > 0 { result = string(purl[0]) @@ -407,7 +407,7 @@ func craComponentOtherUniqIds(component sbom.GetComponent) *record { return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) } - cpes := component.Cpes() + cpes := component.GetCpes() if len(cpes) > 0 { result = string(cpes[0]) diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index d9cec06..4f942d7 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -16,79 +16,60 @@ package compliance import ( "context" - "strings" + "fmt" "time" "github.com/interlynk-io/sbomqs/pkg/logger" "github.com/interlynk-io/sbomqs/pkg/sbom" - "github.com/samber/lo" ) -func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) *db { +func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) { log := logger.FromContext(ctx) log.Debug("compliance.ntiaResult()") db := newDB() - db.addRecord(ntiaSpec(doc)) - db.addRecord(ntiaSpecVersion(doc)) + db.addRecord(ntiaAutomationSpec(doc)) db.addRecord(ntiaSbomCreator(doc)) db.addRecord(ntiaSbomCreatedTimestamp(doc)) db.addRecords(ntiaComponents(doc)) - db.addRecord(ntiaBuildPhase(doc)) - db.addRecord(ntiaSbomDepth(doc)) - db.addRecord(ntiaComponentHash(doc)) - db.addRecord(ntiaComponentlicense(doc)) + // db.addRecord(ntiaBuildPhase(doc)) + // db.addRecord(ntiaComponentHash(doc)) + // db.addRecord(ntiaComponentlicense(doc)) - return db -} + if outFormat == "json" { + ntiaJsonReport(db, fileName) + } -// format -func ntiaSpec(doc sbom.Document) *record { - v := doc.Spec().GetSpecType() - v_to_lower := strings.Trim(strings.ToLower(v), " ") - result := "" - score := 0.0 + if outFormat == "basic" { + ntiaBasicReport(db, fileName) + } - if v_to_lower == "spdx" { - result = v - score = 10.0 - } else if v_to_lower == "cyclonedx" { - result = v - score = 10.0 + if outFormat == "detailed" { + ntiaDetailedReport(db, fileName) } - return newRecordStmt(SBOM_SPEC, "SBOM format", result, score) } -func ntiaSpecVersion(doc sbom.Document) *record { +// format +func ntiaAutomationSpec(doc sbom.Document) *record { spec := doc.Spec().GetSpecType() - version := doc.Spec().GetVersion() - result, score := "", 0.0 - if spec == "spdx" { - count := lo.Count(valid_cra_spdx_versions, version) - if count > 0 { - result = version - score = 10.0 - } - } else if spec == "cyclonedx" { - count := lo.Count(valid_cra_cdx_versions, version) - if count > 0 { - result = version - score = 10.0 - } + if fileFormat := doc.Spec().FileFormat(); fileFormat == "json" || fileFormat == "tag-value" { + result = spec + ", " + fileFormat + score = 10.0 + } else { + result = spec + ", " + fileFormat } - return newRecordStmt(SBOM_SPEC_VERSION, "SBOM format", result, score) + return newRecordStmt(SBOM_MACHINE_FORMAT, "Automation Support", result, score) } // Required Sbom stuffs func ntiaSbomCreator(doc sbom.Document) *record { spec := doc.Spec().GetSpecType() + result, score := "", 0.0 if spec == "spdx" { - result := "" - score := 0.0 name := "" if tools := doc.Tools(); tools != nil { for _, tool := range tools { @@ -99,22 +80,20 @@ func ntiaSbomCreator(doc sbom.Document) *record { } } } - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } else if spec == "cyclonedx" { - result := "" - score := 0.0 for _, author := range doc.Authors() { - if author.Email() != "" { - result = author.Email() + if author.GetEmail() != "" { + result = author.GetEmail() score = 10.0 break } } if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } supplier := doc.Supplier() @@ -126,7 +105,7 @@ func ntiaSbomCreator(doc sbom.Document) *record { } if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } if supplier.GetUrl() != "" { @@ -135,7 +114,7 @@ func ntiaSbomCreator(doc sbom.Document) *record { } if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } if supplier.GetContacts() != nil { @@ -148,7 +127,7 @@ func ntiaSbomCreator(doc sbom.Document) *record { } if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } } } @@ -162,7 +141,7 @@ func ntiaSbomCreator(doc sbom.Document) *record { } if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } if manufacturer.Url() != "" { @@ -171,7 +150,7 @@ func ntiaSbomCreator(doc sbom.Document) *record { } if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } if manufacturer.Contacts() != nil { @@ -184,12 +163,12 @@ func ntiaSbomCreator(doc sbom.Document) *record { } if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM authors", result, score) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } } } } - return newRecordStmt(SBOM_CREATOR, "Required SBOM fields", "", 0.0) + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } func ntiaSbomCreatedTimestamp(doc sbom.Document) *record { @@ -204,11 +183,29 @@ func ntiaSbomCreatedTimestamp(doc sbom.Document) *record { score = 10.0 } } - return newRecordStmt(SBOM_TIMESTAMP, "Required SBOM fields", result, score) + return newRecordStmt(SBOM_TIMESTAMP, "SBOM Data Fields", result, score) } // Required component stuffs -func ntiaComponents(doc sbom.Document) []*record {} +func ntiaComponents(doc sbom.Document) []*record { + records := []*record{} + + if len(doc.Components()) == 0 { + records := append(records, newRecordStmt(SBOM_COMPONENTS, "doc", "", 0.0)) + return records + } + + for _, component := range doc.Components() { + records = append(records, ntiaComponentName(component)) + records = append(records, ntiaComponentCreator(doc, component)) + records = append(records, ntiaComponentVersion(component)) + records = append(records, ntiaComponentOtherUniqIds(doc, component)) + records = append(records, ntiaComponentDependencies(doc, component)) + + } + records = append(records, newRecordStmt(SBOM_COMPONENTS, "SBOM Data Fields", "present", 10.0)) + return records +} func ntiaComponentName(component sbom.GetComponent) *record { if result := component.GetName(); result != "" { @@ -313,27 +310,99 @@ func ntiaComponentVersion(component sbom.GetComponent) *record { return newRecordStmt(COMP_VERSION, component.GetID(), "", 0.0) } -func ntiaComponent(doc sbom.Document) *record {} -func ntiaComponentDepth(doc sbom.Document) *record {} -func ntiaComponentOtherUniqIds(doc sbom.Document) *record {} -// Recommended sbom stuffs -// lifecycle -func ntiaBuildPhase(doc sbom.Document) *record { - lifecycles := doc.Lifecycles() - result, score := "", 0.0 +// func ntiaComponentDepth(doc sbom.Document) *record {} - found := lo.Count(lifecycles, "build") +func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) *record { + spec := doc.Spec().GetSpecType() - if found > 0 { - result = "build" - score = 10.0 + if spec == "spdx" { + result, score := "", 0.0 + if relation := doc.Relations(); relation != nil { + for _, rel := range relation { + if rel.GetFrom() != "" && rel.GetTo() != "" { + result = rel.GetFrom() + ", " + rel.GetTo() + score = 10 + return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) + } + } + } + + return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) + + } else if spec == "cyclonedx" { + return craComponentDepth(component) } - return newRecordStmt(SBOM_BUILD, "doc", result, score) + return newRecordStmt(COMP_DEPTH, component.GetID(), "", 0.0) } -func ntiaSbomDepth(doc sbom.Document) *record {} + +func ntiaComponentOtherUniqIds(doc sbom.Document, component sbom.GetComponent) *record { + spec := doc.Spec().GetSpecType() + + if spec == "spdx" { + result, score, totalElements, containPurlElement := "", 0.0, 0, 0 + + if extRefs := component.ExternalReferences(); extRefs != nil { + for _, extRef := range extRefs { + totalElements++ + result = extRef.GetRefType() + if result == "purl" { + containPurlElement++ + } + } + } + if containPurlElement != 0 { + score = (float64(containPurlElement) / float64(totalElements)) * 10.0 + x := fmt.Sprintf(":(%d/%d)", containPurlElement, totalElements) + result = result + x + } + return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + } else if spec == "cyclonedx" { + result := "" + score := 0.0 + + purl := component.GetPurls() + + if len(purl) > 0 { + result = string(purl[0]) + score = 10.0 + + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + } + + cpes := component.GetCpes() + + if len(cpes) > 0 { + result = string(cpes[0]) + score = 10.0 + + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + } + + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), "", 0.0) + } + return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetID(), "", 0.0) +} + +// Recommended sbom stuffs +// lifecycle +// func ntiaBuildPhase(doc sbom.Document) *record { +// lifecycles := doc.Lifecycles() +// result, score := "", 0.0 + +// found := lo.Count(lifecycles, "build") + +// if found > 0 { +// result = "build" +// score = 10.0 +// } + +// return newRecordStmt(SBOM_BUILD, "doc", result, score) +// } + +// func ntiaSbomDepth(doc sbom.Document) *record {} // Recommended component stuffs -func ntiaComponentHash(doc sbom.Document) *record {} -func ntiaComponentlicense(doc sbom.Document) *record {} +// func ntiaComponentHash(doc sbom.Document) *record {} +// func ntiaComponentlicense(doc sbom.Document) *record {} diff --git a/pkg/compliance/ntia_report.go b/pkg/compliance/ntia_report.go new file mode 100644 index 0000000..a1771c9 --- /dev/null +++ b/pkg/compliance/ntia_report.go @@ -0,0 +1,140 @@ +package compliance + +import ( + "encoding/json" + "fmt" + "os" + "time" + + "github.com/google/uuid" + "github.com/olekukonko/tablewriter" + "sigs.k8s.io/release-utils/version" +) + +var ntiaSectionDetails = map[int]ntiaSection{ + SBOM_MACHINE_FORMAT: {Title: "Automation Support", Id: "2.1", Required: true, DataField: "Machine-Readable Formats"}, + SBOM_CREATOR: {Title: "Required fields sboms ", Id: "1.1", Required: true, DataField: "Author"}, + SBOM_TIMESTAMP: {Title: "Required fields sboms", Id: "1.2", Required: true, DataField: "Timestamp"}, + SBOM_COMPONENTS: {Title: "Required sbom component", Id: "1.4", Required: true, DataField: "Packages"}, + COMP_NAME: {Title: "Required fields components", Id: "1.5", Required: true, DataField: "Package Name"}, + COMP_DEPTH: {Title: "Required fields components", Id: "1.3", Required: true, DataField: "Dependencies on other components"}, + COMP_CREATOR: {Title: "Required fields component", Id: "1.6", Required: true, DataField: "Package Supplier"}, + PACK_SUPPLIER: {Title: "Required fields component", Id: "1.6", Required: true, DataField: "Package Supplier"}, + COMP_VERSION: {Title: "Required fields components", Id: "1.7", Required: true, DataField: "Package Version"}, + COMP_OTHER_UNIQ_IDS: {Title: "Required fields component", Id: "1.8", Required: true, DataField: "Other Uniq IDs"}, +} + +type ntiaSection struct { + Title string `json:"section_title"` + Id string `json:"section_id"` + DataField string `json:"section_data_field"` + Required bool `json:"required"` + ElementId string `json:"element_id"` + ElementResult string `json:"element_result"` + Score float64 `json:"score"` +} + +type ntiaComplianceReport struct { + Name string `json:"report_name"` + Subtitle string `json:"subtitle"` + Revision string `json:"revision"` + Run run `json:"run"` + Tool tool `json:"tool"` + Summary Summary `json:"summary"` + Sections []ntiaSection `json:"sections"` +} + +func newNtiaJsonReport() *ntiaComplianceReport { + return &ntiaComplianceReport{ + Name: "NTIA Compliance Report", + Subtitle: "Part 2: Software Bill of Materials (SBOM)", + Revision: "", + Run: run{ + Id: uuid.New().String(), + GeneratedAt: time.Now().UTC().Format(time.RFC3339), + FileName: "", + EngineVersion: "1", + }, + Tool: tool{ + Name: "sbomqs", + Version: version.GetVersionInfo().GitVersion, + Vendor: "Interlynk (support@interlynk.io)", + }, + } +} + +func ntiaJsonReport(db *db, fileName string) { + jr := newNtiaJsonReport() + jr.Run.FileName = fileName + + score := ntiaAggregateScore(db) + summary := Summary{} + summary.MaxScore = 10.0 + summary.TotalScore = score.totalScore() + summary.TotalRequiredScore = score.totalRequiredScore() + summary.TotalOptionalScore = score.totalOptionalScore() + + jr.Summary = summary + jr.Sections = ntiaConstructSections(db) + + o, _ := json.MarshalIndent(jr, "", " ") + fmt.Println(string(o)) +} + +func ntiaConstructSections(db *db) []ntiaSection { + var sections []ntiaSection + allIds := db.getAllIds() + for _, id := range allIds { + records := db.getRecordsById(id) + + for _, r := range records { + section := ntiaSectionDetails[r.check_key] + new_section := ntiaSection{ + Title: section.Title, + Id: section.Id, + DataField: section.DataField, + Required: section.Required, + } + score := ntiaKeyIdScore(db, r.check_key, r.id) + new_section.Score = score.totalScore() + if r.id == "doc" { + new_section.ElementId = "sbom" + } else { + new_section.ElementId = r.id + } + + new_section.ElementResult = r.check_value + + sections = append(sections, new_section) + } + } + return sections +} + +func ntiaDetailedReport(db *db, fileName string) { + table := tablewriter.NewWriter(os.Stdout) + score := ntiaAggregateScore(db) + + fmt.Printf("NTIA Report\n") + fmt.Printf("Compliance score by Interlynk Score:%0.1f RequiredScore:%0.1f OptionalScore:%0.1f for %s\n", score.totalScore(), score.totalRequiredScore(), score.totalOptionalScore(), fileName) + fmt.Printf("* indicates optional fields\n") + table.SetHeader([]string{"ELEMENT ID", "Section ID", "NTIA minimum elements", "Result", "Score"}) + table.SetRowLine(true) + table.SetAutoMergeCellsByColumnIndex([]int{0}) + + sections := ntiaConstructSections(db) + for _, section := range sections { + sectionId := section.Id + if !section.Required { + sectionId = sectionId + "*" + } + table.Append([]string{section.ElementId, sectionId, section.DataField, section.ElementResult, fmt.Sprintf("%0.1f", section.Score)}) + } + table.Render() +} + +func ntiaBasicReport(db *db, fileName string) { + score := ntiaAggregateScore(db) + fmt.Printf("NTIA Report\n") + fmt.Printf("Score:%0.1f RequiredScore:%0.1f OptionalScore:%0.1f for %s\n", score.totalScore(), score.totalRequiredScore(), score.totalOptionalScore(), fileName) +} diff --git a/pkg/compliance/ntia_score.go b/pkg/compliance/ntia_score.go new file mode 100644 index 0000000..a94c5c3 --- /dev/null +++ b/pkg/compliance/ntia_score.go @@ -0,0 +1,128 @@ +package compliance + +type ntiaScoreResult struct { + id string + requiredScore float64 + optionalScore float64 + requiredRecords int + optionalRecords int +} + +func newNtiaScoreResult(id string) *ntiaScoreResult { + return &ntiaScoreResult{id: id} +} + +func (r *ntiaScoreResult) totalScore() float64 { + if r.requiredRecords == 0 && r.optionalRecords == 0 { + return 0.0 + } + + if r.requiredRecords != 0 && r.optionalRecords != 0 { + return (r.totalRequiredScore() + r.totalOptionalScore()) / 2 + } + + if r.requiredRecords == 0 && r.optionalRecords != 0 { + return r.totalOptionalScore() + } + + return r.totalRequiredScore() +} + +func (r *ntiaScoreResult) totalRequiredScore() float64 { + if r.requiredRecords == 0 { + return 0.0 + } + + return r.requiredScore / float64(r.requiredRecords) +} + +func (r *ntiaScoreResult) totalOptionalScore() float64 { + if r.optionalRecords == 0 { + return 0.0 + } + + return r.optionalScore / float64(r.optionalRecords) +} + +func ntiaKeyIdScore(db *db, key int, id string) *ntiaScoreResult { + records := db.getRecordsByKeyId(key, id) + + if len(records) == 0 { + return newNtiaScoreResult(id) + } + + required_score := 0.0 + optional_score := 0.0 + + required_recs := 0 + optional_recs := 0 + + for _, r := range records { + if r.required { + required_score += r.score + required_recs += 1 + } else { + optional_score += r.score + optional_recs += 1 + } + } + + return &ntiaScoreResult{ + id: id, + requiredScore: required_score, + optionalScore: optional_score, + requiredRecords: required_recs, + optionalRecords: optional_recs, + } +} + +func ntiaAggregateScore(db *db) *ntiaScoreResult { + var results []ntiaScoreResult + var finalResult ntiaScoreResult + + ids := db.getAllIds() + for _, id := range ids { + results = append(results, *ntiaIdScore(db, id)) + } + + for _, r := range results { + finalResult.requiredScore += r.requiredScore + finalResult.optionalScore += r.optionalScore + finalResult.requiredRecords += r.requiredRecords + finalResult.optionalRecords += r.optionalRecords + } + + return &finalResult +} + +func ntiaIdScore(db *db, id string) *ntiaScoreResult { + records := db.getRecordsById(id) + + if len(records) == 0 { + return newNtiaScoreResult(id) + } + + required_score := 0.0 + optional_score := 0.0 + + required_recs := 0 + optional_recs := 0 + + for _, r := range records { + if r.required { + required_score += r.score + required_recs += 1 + } else { + optional_score += r.score + optional_recs += 1 + } + } + + return &ntiaScoreResult{ + id: id, + requiredScore: required_score, + optionalScore: optional_score, + requiredRecords: required_recs, + optionalRecords: optional_recs, + } +} diff --git a/pkg/compliance/ntia_test.go b/pkg/compliance/ntia_test.go new file mode 100644 index 0000000..17cd694 --- /dev/null +++ b/pkg/compliance/ntia_test.go @@ -0,0 +1,562 @@ +package compliance + +import ( + "testing" + + "github.com/interlynk-io/sbomqs/pkg/purl" + "github.com/interlynk-io/sbomqs/pkg/sbom" + "gotest.tools/assert" +) + +func createSpdxDummyDocumentNtia() sbom.Document { + s := sbom.NewSpec() + s.Version = "SPDX-2.3" + s.SpecType = "spdx" + s.CreationTimestamp = "2023-05-04T09:33:40Z" + + var creators []sbom.GetTool + creator := sbom.Tool{ + Name: "syft", + } + creators = append(creators, creator) + + pack := sbom.NewComponent() + pack.Version = "v0.7.1" + pack.Name = "core-js" + + supplier := sbom.Supplier{ + Email: "vivekkumarsahu650@gmail.com", + } + pack.Supplier = supplier + + extRef := sbom.ExternalReference{ + RefType: "purl", + } + + var externalReferences []sbom.GetExternalReference + externalReferences = append(externalReferences, extRef) + pack.ExternalRefs = externalReferences + + var packages []sbom.GetComponent + packages = append(packages, pack) + + depend := sbom.Relation{ + From: "SPDXRef-Package-go-module-github.com-abc", + To: "SPDXRef-Package-go-module-github.com-xyz", + } + var dependencies []sbom.GetRelation + dependencies = append(dependencies, depend) + + doc := sbom.SpdxDoc{ + SpdxSpec: s, + Comps: packages, + SpdxTools: creators, + Rels: dependencies, + } + return doc +} + +type desiredNtia struct { + score float64 + result string + key int + id string +} + +func TestNtiaSpdxSbomPass(t *testing.T) { + doc := createSpdxDummyDocumentNtia() + testCases := []struct { + actual *record + expected desiredNtia + }{ + { + actual: ntiaAutomationSpec(doc), + expected: desiredNtia{ + score: 10.0, + result: "spdx", + key: SBOM_SPEC, + id: "SBOM format", + }, + }, + { + actual: ntiaSbomCreator(doc), + expected: desiredNtia{ + score: 10.0, + result: "syft", + key: SBOM_CREATOR, + id: "doc", + }, + }, + { + actual: ntiaSbomCreatedTimestamp(doc), + expected: desiredNtia{ + score: 10.0, + result: "2023-05-04T09:33:40Z", + key: SBOM_TIMESTAMP, + id: "doc", + }, + }, + { + actual: ntiaComponentCreator(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "vivekkumarsahu650@gmail.com", + key: PACK_SUPPLIER, + id: doc.Components()[0].GetID(), + }, + }, + + { + actual: ntiaComponentName(doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "core-js", + key: COMP_NAME, + id: doc.Components()[0].GetID(), + }, + }, + { + actual: ntiaComponentVersion(doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "v0.7.1", + key: COMP_VERSION, + id: doc.Components()[0].GetID(), + }, + }, + { + actual: ntiaComponentOtherUniqIds(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "purl:(1/1)", + key: PACK_EXT_REF, + id: doc.Components()[0].GetID(), + }, + }, + { + actual: ntiaComponentDependencies(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "SPDXRef-Package-go-module-github.com-abc, SPDXRef-Package-go-module-github.com-xyz", + key: COMP_DEPTH, + id: doc.Components()[0].GetID(), + }, + }, + } + + for _, test := range testCases { + assert.Equal(t, test.expected.score, test.actual.score) + assert.Equal(t, test.expected.key, test.actual.check_key) + assert.Equal(t, test.expected.id, test.actual.id) + assert.Equal(t, test.expected.result, test.actual.check_value) + } +} + +func createCdxDummyDocumentNtia() sbom.Document { + cdxSpec := sbom.NewSpec() + cdxSpec.Version = "1.4" + cdxSpec.SpecType = "cyclonedx" + cdxSpec.CreationTimestamp = "2023-05-04T09:33:40Z" + + var authors []sbom.GetAuthor + author := sbom.Author{ + Email: "vivekkumarsahu650@gmail.com", + } + authors = append(authors, author) + + comp := sbom.NewComponent() + comp.Version = "v0.7.1" + comp.Name = "core-js" + + supplier := sbom.Supplier{ + Email: "vivekkumarsahu650@gmail.com", + } + comp.Supplier = supplier + + npurl := purl.NewPURL("vivek") + + comp.Purls = []purl.PURL{npurl} + + extRef := sbom.ExternalReference{ + RefType: "purl", + } + + var externalReferences []sbom.GetExternalReference + externalReferences = append(externalReferences, extRef) + comp.ExternalRefs = externalReferences + + var components []sbom.GetComponent + components = append(components, comp) + + doc := sbom.CdxDoc{ + CdxSpec: cdxSpec, + Comps: components, + CdxAuthors: authors, + } + return doc +} + +func TestNtiaCdxSbomPass(t *testing.T) { + doc := createCdxDummyDocumentNtia() + testCases := []struct { + actual *record + expected desiredNtia + }{ + { + actual: ntiaAutomationSpec(doc), + expected: desiredNtia{ + score: 10.0, + result: "cyclonedx", + key: SBOM_SPEC, + id: "SBOM format", + }, + }, + { + actual: ntiaSbomCreator(doc), + expected: desiredNtia{ + score: 10.0, + result: "vivekkumarsahu650@gmail.com", + key: SBOM_CREATOR, + id: "doc", + }, + }, + { + actual: ntiaSbomCreatedTimestamp(doc), + expected: desiredNtia{ + score: 10.0, + result: "2023-05-04T09:33:40Z", + key: SBOM_TIMESTAMP, + id: "doc", + }, + }, + { + actual: ntiaComponentCreator(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "vivekkumarsahu650@gmail.com", + key: COMP_CREATOR, + id: doc.Components()[0].GetID(), + }, + }, + + { + actual: ntiaComponentName(doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "core-js", + key: COMP_NAME, + id: doc.Components()[0].GetID(), + }, + }, + { + actual: ntiaComponentVersion(doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "v0.7.1", + key: COMP_VERSION, + id: doc.Components()[0].GetID(), + }, + }, + { + actual: ntiaComponentOtherUniqIds(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "vivek", + key: COMP_OTHER_UNIQ_IDS, + id: doc.Components()[0].GetID(), + }, + }, + } + for _, test := range testCases { + assert.Equal(t, test.expected.score, test.actual.score) + assert.Equal(t, test.expected.key, test.actual.check_key) + assert.Equal(t, test.expected.id, test.actual.id) + assert.Equal(t, test.expected.result, test.actual.check_value) + } +} + +// func createFailureDummyDocumentNtia() sbom.Document { +// s := sbom.NewSpec() +// s.Version = "" +// s.Format = "xml" +// s.SpecType = "cyclonedx" +// s.Name = "" +// s.Namespace = "" +// s.Organization = "" +// s.CreationTimestamp = "wrong-time-format" +// s.Spdxid = "" +// s.Comment = "" +// lics := licenses.CreateCustomLicense("", "") +// s.Licenses = append(s.Licenses, lics) + +// var tools []sbom.GetTool +// tool := sbom.Tool{ +// Name: "", +// } +// tools = append(tools, tool) + +// pack := sbom.NewComponent() +// pack.Version = "" +// pack.Name = "" +// pack.Spdxid = "" +// pack.CopyRight = "NOASSERTION" +// pack.FileAnalyzed = false +// pack.Id = "" +// pack.PackageLicenseConcluded = "NONE" +// pack.PackageLicenseDeclared = "NOASSERTION" +// pack.DownloadLocation = "" + +// supplier := sbom.Supplier{ +// Email: "", +// } +// pack.Supplier = supplier + +// checksum := sbom.Checksum{ +// Alg: "SHA-1", +// Content: "443238d9cf19f77ccc8cdda3ba5421ea9ea2bc78", +// } + +// var checksums []sbom.GetChecksum +// checksums = append(checksums, checksum) +// pack.Checksums = checksums + +// extRef := sbom.ExternalReference{ +// RefType: "cpe23Type", +// } +// var externalReferences []sbom.GetExternalReference +// externalReferences = append(externalReferences, extRef) +// pack.ExternalRefs = externalReferences + +// var packages []sbom.GetComponent +// packages = append(packages, pack) + +// doc := sbom.SpdxDoc{ +// SpdxSpec: s, +// Comps: packages, +// SpdxTools: tools, +// } +// return doc +// } + +// func TestOctSbomFail(t *testing.T) { +// doc := createFailureDummyDocument() +// testCases := []struct { +// actual *record +// expected desired +// }{ +// { +// actual: octSpec(doc), +// expected: desired{ +// score: 0.0, +// result: "cyclonedx", +// key: SBOM_SPEC, +// id: "SBOM Format", +// }, +// }, +// { +// actual: octSbomName(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_NAME, +// id: "SPDX Elements", +// }, +// }, +// { +// actual: octSbomNamespace(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_NAMESPACE, +// id: "SPDX Elements", +// }, +// }, +// { +// actual: octSbomOrganization(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_ORG, +// id: "SBOM Build Information", +// }, +// }, +// { +// actual: octSbomComment(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_COMMENT, +// id: "SPDX Elements", +// }, +// }, +// { +// actual: octSbomTool(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_TOOL, +// id: "SBOM Build Information", +// }, +// }, +// { +// actual: octSbomLicense(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_LICENSE, +// id: "SPDX Elements", +// }, +// }, +// { +// actual: octSpecVersion(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_SPEC_VERSION, +// id: "SPDX Elements", +// }, +// }, +// { +// actual: octCreatedTimestamp(doc), +// expected: desired{ +// score: 0.0, +// result: "wrong-time-format", +// key: SBOM_TIMESTAMP, +// id: "SPDX Elements", +// }, +// }, +// { +// actual: octSpecSpdxID(doc), +// expected: desired{ +// score: 0.0, +// result: "", +// key: SBOM_SPDXID, +// id: "SPDX Elements", +// }, +// }, +// { +// actual: octMachineFormat(doc), +// expected: desired{ +// score: 0.0, +// result: "cyclonedx, xml", +// key: SBOM_MACHINE_FORMAT, +// id: "Machine Readable Data Format", +// }, +// }, +// { +// actual: octHumanFormat(doc), +// expected: desired{ +// score: 0.0, +// result: "xml", +// key: SBOM_HUMAN_FORMAT, +// id: "Human Readable Data Format", +// }, +// }, +// { +// actual: octPackageName(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "", +// key: PACK_NAME, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageVersion(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "", +// key: PACK_VERSION, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageSpdxID(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "", +// key: PACK_SPDXID, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageSupplier(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "", +// key: PACK_SUPPLIER, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageHash(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "", +// key: PACK_HASH, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageExternalRefs(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "cpe23Type", +// key: PACK_EXT_REF, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageCopyright(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "NOASSERTION", +// key: PACK_COPYRIGHT, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageFileAnalyzed(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "no", +// key: PACK_FILE_ANALYZED, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageConLicense(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "NONE", +// key: PACK_LICENSE_CON, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageDecLicense(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "NOASSERTION", +// key: PACK_LICENSE_DEC, +// id: doc.Components()[0].GetID(), +// }, +// }, +// { +// actual: octPackageDownloadUrl(doc.Components()[0]), +// expected: desired{ +// score: 0.0, +// result: "", +// key: PACK_DOWNLOAD_URL, +// id: doc.Components()[0].GetID(), +// }, +// }, +// } + +// for _, test := range testCases { +// assert.Equal(t, test.expected.score, test.actual.score) +// assert.Equal(t, test.expected.key, test.actual.check_key) +// assert.Equal(t, test.expected.id, test.actual.id) +// assert.Equal(t, test.expected.result, test.actual.check_value) +// } +// } diff --git a/pkg/sbom/author.go b/pkg/sbom/author.go index 7b6de0e..c8d8ea6 100644 --- a/pkg/sbom/author.go +++ b/pkg/sbom/author.go @@ -15,25 +15,26 @@ package sbom //counterfeiter:generate . Author -type Author interface { - Name() string - Type() string - Email() string +type GetAuthor interface { + GetName() string + GetType() string + GetEmail() string } -type author struct { - name string - email string - authorType string //person or org +type Author struct { + Name string + Email string + AuthorType string // person or org } -func (a author) Name() string { - return a.name +func (a Author) GetName() string { + return a.Name } -func (a author) Type() string { - return a.authorType + +func (a Author) GetType() string { + return a.AuthorType } -func (a author) Email() string { - return a.email +func (a Author) GetEmail() string { + return a.Email } diff --git a/pkg/sbom/cdx.go b/pkg/sbom/cdx.go index 1fcecd5..71632e4 100644 --- a/pkg/sbom/cdx.go +++ b/pkg/sbom/cdx.go @@ -34,14 +34,14 @@ var ( cdx_primary_purpose = []string{"application", "framework", "library", "container", "operating-system", "device", "firmware", "file"} ) -type cdxDoc struct { +type CdxDoc struct { doc *cydx.BOM format FileFormat ctx context.Context - spec *Specs - comps []GetComponent - authors []Author - tools []GetTool + CdxSpec *Specs + Comps []GetComponent + CdxAuthors []GetAuthor + CdxTools []GetTool rels []GetRelation logs []string primaryComponent bool @@ -75,7 +75,7 @@ func newCDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Documen err = fmt.Errorf("unsupported cdx file format: %s", string(format)) } - doc := &cdxDoc{ + doc := &CdxDoc{ doc: bom, format: format, ctx: ctx, @@ -85,47 +85,47 @@ func newCDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Documen return doc, err } -func (c cdxDoc) Spec() Spec { - return *c.spec +func (c CdxDoc) Spec() Spec { + return *c.CdxSpec } -func (c cdxDoc) Components() []GetComponent { - return c.comps +func (c CdxDoc) Components() []GetComponent { + return c.Comps } -func (c cdxDoc) Authors() []Author { - return c.authors +func (c CdxDoc) Authors() []GetAuthor { + return c.CdxAuthors } -func (c cdxDoc) Tools() []GetTool { - return c.tools +func (c CdxDoc) Tools() []GetTool { + return c.CdxTools } -func (c cdxDoc) Relations() []GetRelation { +func (c CdxDoc) Relations() []GetRelation { return c.rels } -func (c cdxDoc) Logs() []string { +func (c CdxDoc) Logs() []string { return c.logs } -func (c cdxDoc) PrimaryComponent() bool { +func (c CdxDoc) PrimaryComponent() bool { return c.primaryComponent } -func (c cdxDoc) Lifecycles() []string { +func (c CdxDoc) Lifecycles() []string { return c.lifecycles } -func (c cdxDoc) Supplier() GetSupplier { +func (c CdxDoc) Supplier() GetSupplier { return c.supplier } -func (c cdxDoc) Manufacturer() Manufacturer { +func (c CdxDoc) Manufacturer() Manufacturer { return c.manufacturer } -func (c *cdxDoc) parse() { +func (c *CdxDoc) parse() { c.parseDoc() c.parseSpec() c.parseAuthors() @@ -138,11 +138,11 @@ func (c *cdxDoc) parse() { c.parseComps() } -func (c *cdxDoc) addToLogs(log string) { +func (c *CdxDoc) addToLogs(log string) { c.logs = append(c.logs, log) } -func (c *cdxDoc) parseDoc() { +func (c *CdxDoc) parseDoc() { if c.doc == nil { c.addToLogs("cdx doc is not parsable") return @@ -171,7 +171,7 @@ func (c *cdxDoc) parseDoc() { }) } -func (c *cdxDoc) parseSpec() { +func (c *CdxDoc) parseSpec() { sp := NewSpec() sp.Format = string(c.format) sp.Version = c.doc.SpecVersion.String() @@ -191,10 +191,10 @@ func (c *cdxDoc) parseSpec() { sp.uri = fmt.Sprintf("%s/%d", c.doc.SerialNumber, c.doc.Version) } - c.spec = sp + c.CdxSpec = sp } -func (c *cdxDoc) requiredFields() bool { +func (c *CdxDoc) requiredFields() bool { if c.doc == nil { c.addToLogs("cdx doc is not parsable") return false @@ -229,7 +229,7 @@ func (c *cdxDoc) requiredFields() bool { return true } -func copyC(cdxc *cydx.Component, c *cdxDoc) *Component { +func copyC(cdxc *cydx.Component, c *CdxDoc) *Component { if cdxc == nil { return nil } @@ -242,14 +242,14 @@ func copyC(cdxc *cydx.Component, c *cdxDoc) *Component { ncpe := cpe.NewCPE(cdxc.CPE) if ncpe.Valid() { - nc.cpes = []cpe.CPE{ncpe} + nc.Cpes = []cpe.CPE{ncpe} } else { c.addToLogs(fmt.Sprintf("cdx base doc component %s at index %d invalid cpes found", cdxc.Name, -1)) } npurl := purl.NewPURL(cdxc.PackageURL) if npurl.Valid() { - nc.purls = []purl.PURL{npurl} + nc.Purls = []purl.PURL{npurl} } else { c.addToLogs(fmt.Sprintf("cdx base doc component %s at index %d invalid purl found", cdxc.Name, -1)) } @@ -324,7 +324,7 @@ func copyC(cdxc *cydx.Component, c *cdxDoc) *Component { nc.hasRelationships = fromRelsPresent(c.rels, cdxc.BOMRef) if c.compositions != nil { if comp, ok := c.compositions[cdxc.BOMRef]; ok { - nc.relationshipState = compNormalise(comp) + nc.RelationshipState = compNormalise(comp) } } @@ -332,8 +332,8 @@ func copyC(cdxc *cydx.Component, c *cdxDoc) *Component { return nc } -func (c *cdxDoc) parseComps() { - c.comps = []GetComponent{} +func (c *CdxDoc) parseComps() { + c.Comps = []GetComponent{} comps := map[string]*Component{} if c.doc.Metadata != nil && c.doc.Metadata.Component != nil { walkComponents(&[]cydx.Component{*c.doc.Metadata.Component}, c, comps) @@ -344,11 +344,11 @@ func (c *cdxDoc) parseComps() { } for _, v := range comps { - c.comps = append(c.comps, v) + c.Comps = append(c.Comps, v) } } -func walkComponents(comps *[]cydx.Component, doc *cdxDoc, store map[string]*Component) { +func walkComponents(comps *[]cydx.Component, doc *CdxDoc, store map[string]*Component) { if comps == nil { return } @@ -379,7 +379,7 @@ func compID(comp *cydx.Component) string { return id.String() } -func (c *cdxDoc) pkgRequiredFields(comp *cydx.Component) bool { +func (c *CdxDoc) pkgRequiredFields(comp *cydx.Component) bool { if string(comp.Type) == "" { c.addToLogs(fmt.Sprintf("cdx doc comp %s missing type field", comp.Name)) return false @@ -393,7 +393,7 @@ func (c *cdxDoc) pkgRequiredFields(comp *cydx.Component) bool { return true } -func (c *cdxDoc) checksums(comp *cydx.Component) []GetChecksum { +func (c *CdxDoc) checksums(comp *cydx.Component) []GetChecksum { chks := []GetChecksum{} if len(lo.FromPtr(comp.Hashes)) == 0 { @@ -410,7 +410,7 @@ func (c *cdxDoc) checksums(comp *cydx.Component) []GetChecksum { return chks } -func (c *cdxDoc) licenses(comp *cydx.Component) []licenses.License { +func (c *CdxDoc) licenses(comp *cydx.Component) []licenses.License { return aggregate_licenses(lo.FromPtr(comp.Licenses)) } @@ -439,8 +439,8 @@ func aggregate_licenses(clicenses cydx.Licenses) []licenses.License { return lics } -func (c *cdxDoc) parseTool() { - c.tools = []GetTool{} +func (c *CdxDoc) parseTool() { + c.CdxTools = []GetTool{} if c.doc.Metadata == nil { return @@ -454,34 +454,34 @@ func (c *cdxDoc) parseTool() { t := Tool{} t.Name = tt.Name t.Version = tt.Version - c.tools = append(c.tools, t) + c.CdxTools = append(c.CdxTools, t) } for _, ct := range lo.FromPtr(c.doc.Metadata.Tools.Components) { t := Tool{} t.Name = ct.Name t.Version = ct.Version - c.tools = append(c.tools, t) + c.CdxTools = append(c.CdxTools, t) } } -func (c *cdxDoc) parseAuthors() { - c.authors = []Author{} +func (c *CdxDoc) parseAuthors() { + c.CdxAuthors = []GetAuthor{} if c.doc.Metadata == nil { return } for _, auth := range lo.FromPtr(c.doc.Metadata.Authors) { - a := author{} - a.name = auth.Name - a.email = auth.Email - a.authorType = "person" - c.authors = append(c.authors, a) + a := Author{} + a.Name = auth.Name + a.Email = auth.Email + a.AuthorType = "person" + c.CdxAuthors = append(c.CdxAuthors, a) } } -func (c *cdxDoc) parseSupplier() { +func (c *CdxDoc) parseSupplier() { if c.doc.Metadata == nil { return } @@ -507,7 +507,7 @@ func (c *cdxDoc) parseSupplier() { c.supplier = supplier } -func (c *cdxDoc) parseManufacturer() { +func (c *CdxDoc) parseManufacturer() { if c.doc.Metadata == nil { return } @@ -533,7 +533,7 @@ func (c *cdxDoc) parseManufacturer() { c.manufacturer = m } -func (c *cdxDoc) parseRels() { +func (c *CdxDoc) parseRels() { c.rels = []GetRelation{} for _, r := range lo.FromPtr(c.doc.Dependencies) { @@ -546,7 +546,7 @@ func (c *cdxDoc) parseRels() { } } -func (c *cdxDoc) assignSupplier(comp *cydx.Component) *Supplier { +func (c *CdxDoc) assignSupplier(comp *cydx.Component) *Supplier { if comp.Supplier == nil { c.addToLogs(fmt.Sprintf("cdx doc comp %s no supplier found", comp.Name)) return nil @@ -574,7 +574,7 @@ func (c *cdxDoc) assignSupplier(comp *cydx.Component) *Supplier { return &supplier } -func (c *cdxDoc) parsePrimaryComponent() { +func (c *CdxDoc) parsePrimaryComponent() { if c.doc.Metadata == nil { return } @@ -587,7 +587,7 @@ func (c *cdxDoc) parsePrimaryComponent() { c.primaryComponentId = c.doc.Metadata.Component.BOMRef } -func (c *cdxDoc) parseCompositions() { +func (c *CdxDoc) parseCompositions() { if c.doc.Compositions == nil { c.compositions = map[string]string{} return diff --git a/pkg/sbom/component.go b/pkg/sbom/component.go index ab74dd5..ad1ec2c 100644 --- a/pkg/sbom/component.go +++ b/pkg/sbom/component.go @@ -26,8 +26,8 @@ type GetComponent interface { GetSupplierName() string GetName() string GetVersion() string - Cpes() []cpe.CPE - Purls() []purl.PURL + GetCpes() []cpe.CPE + GetPurls() []purl.PURL Licenses() []licenses.License GetChecksums() []GetChecksum PrimaryPurpose() string @@ -53,8 +53,8 @@ type Component struct { SupplierName string Name string Version string - cpes []cpe.CPE - purls []purl.PURL + Cpes []cpe.CPE + Purls []purl.PURL licenses []licenses.License Checksums []GetChecksum purpose string @@ -68,7 +68,7 @@ type Component struct { sourceCodeHash string isPrimary bool hasRelationships bool - relationshipState string + RelationshipState string Spdxid string FileAnalyzed bool CopyRight string @@ -93,12 +93,12 @@ func (c Component) GetVersion() string { return c.Version } -func (c Component) Purls() []purl.PURL { - return c.purls +func (c Component) GetPurls() []purl.PURL { + return c.Purls } -func (c Component) Cpes() []cpe.CPE { - return c.cpes +func (c Component) GetCpes() []cpe.CPE { + return c.Cpes } func (c Component) Licenses() []licenses.License { @@ -154,7 +154,7 @@ func (c Component) HasRelationShips() bool { } func (c Component) RelationShipState() string { - return c.relationshipState + return c.RelationshipState } func (c Component) GetSpdxID() string { diff --git a/pkg/sbom/component_test.go b/pkg/sbom/component_test.go index aaa6709..b460f82 100644 --- a/pkg/sbom/component_test.go +++ b/pkg/sbom/component_test.go @@ -33,10 +33,10 @@ func TestGetCpeFromCompo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cp := Component{ - cpes: tt.input, + Cpes: tt.input, } - if len(tt.input) != len(cp.cpes) { - t.Errorf("got %d, want %d", len(cp.cpes), len(tt.input)) + if len(tt.input) != len(cp.Cpes) { + t.Errorf("got %d, want %d", len(cp.Cpes), len(tt.input)) } }) } @@ -54,10 +54,10 @@ func Test_component_Purls(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pl := Component{ - purls: tt.input, + Purls: tt.input, } - if len(tt.input) != len(pl.purls) { - t.Errorf("got %d, want %d", len(pl.purls), len(tt.input)) + if len(tt.input) != len(pl.Purls) { + t.Errorf("got %d, want %d", len(pl.Purls), len(tt.input)) } }) } diff --git a/pkg/sbom/document.go b/pkg/sbom/document.go index c3fb027..c37f292 100644 --- a/pkg/sbom/document.go +++ b/pkg/sbom/document.go @@ -21,7 +21,7 @@ type Document interface { Spec() Spec Components() []GetComponent Relations() []GetRelation - Authors() []Author + Authors() []GetAuthor Tools() []GetTool Logs() []string diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index 66ab4fc..fe4c59b 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -47,9 +47,9 @@ type SpdxDoc struct { ctx context.Context SpdxSpec *Specs Comps []GetComponent - authors []Author + authors []GetAuthor SpdxTools []GetTool - rels []GetRelation + Rels []GetRelation logs []string primaryComponent bool primaryComponentId string @@ -101,7 +101,7 @@ func (s SpdxDoc) Components() []GetComponent { return s.Comps } -func (s SpdxDoc) Authors() []Author { +func (s SpdxDoc) Authors() []GetAuthor { return s.authors } @@ -110,7 +110,7 @@ func (s SpdxDoc) Tools() []GetTool { } func (s SpdxDoc) Relations() []GetRelation { - return s.rels + return s.Rels } func (s SpdxDoc) Logs() []string { @@ -197,8 +197,8 @@ func (s *SpdxDoc) parseComps() { nc.CopyRight = sc.PackageCopyrightText nc.FileAnalyzed = sc.FilesAnalyzed nc.isReqFieldsPresent = s.pkgRequiredFields(index) - nc.purls = s.purls(index) - nc.cpes = s.cpes(index) + nc.Purls = s.purls(index) + nc.Cpes = s.cpes(index) nc.Checksums = s.checksums(index) nc.ExternalRefs = s.externalRefs(index) nc.licenses = s.licenses(index) @@ -238,15 +238,15 @@ func (s *SpdxDoc) parseComps() { return false } - nc.hasRelationships = fromRelsPresent(s.rels, string(sc.PackageSPDXIdentifier)) - nc.relationshipState = "not-specified" + nc.hasRelationships = fromRelsPresent(s.Rels, string(sc.PackageSPDXIdentifier)) + nc.RelationshipState = "not-specified" s.Comps = append(s.Comps, nc) } } func (s *SpdxDoc) parseAuthors() { - s.authors = []Author{} + s.authors = []GetAuthor{} if s.doc.CreationInfo == nil { return @@ -257,20 +257,20 @@ func (s *SpdxDoc) parseAuthors() { if ctType == "tool" { continue } - a := author{} + a := Author{} entity := parseEntity(fmt.Sprintf("%s: %s", c.CreatorType, c.Creator)) if entity != nil { - a.name = entity.name - a.email = entity.email - a.authorType = ctType + a.Name = entity.name + a.Email = entity.email + a.AuthorType = ctType s.authors = append(s.authors, a) } } } func (s *SpdxDoc) parseRels() { - s.rels = []GetRelation{} + s.Rels = []GetRelation{} var err error var aBytes, bBytes []byte @@ -295,7 +295,7 @@ func (s *SpdxDoc) parseRels() { nr.From = string(aBytes) nr.To = string(bBytes) - s.rels = append(s.rels, nr) + s.Rels = append(s.Rels, nr) } } } diff --git a/pkg/scorer/quality.go b/pkg/scorer/quality.go index 1ee1be0..66031c6 100644 --- a/pkg/scorer/quality.go +++ b/pkg/scorer/quality.go @@ -161,7 +161,7 @@ func compWithAnyLookupIdCheck(d sbom.Document, c *check) score { } withAnyLookupId := lo.CountBy(d.Components(), func(c sbom.GetComponent) bool { - if len(c.Cpes()) > 0 || len(c.Purls()) > 0 { + if len(c.GetCpes()) > 0 || len(c.GetPurls()) > 0 { return true } return false @@ -188,7 +188,7 @@ func compWithMultipleIdCheck(d sbom.Document, c *check) score { } withMultipleId := lo.CountBy(d.Components(), func(c sbom.GetComponent) bool { - if len(c.Cpes()) > 0 && len(c.Purls()) > 0 { + if len(c.GetCpes()) > 0 && len(c.GetPurls()) > 0 { return true } return false From a08930925e1440d9587b4c345f31169864df3d59 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Mon, 12 Aug 2024 11:56:09 +0530 Subject: [PATCH 06/19] update variables and function name as per casing Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/ntia_report.go | 12 ++++++------ pkg/compliance/ntia_score.go | 6 +++--- pkg/compliance/ntia_test.go | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/compliance/ntia_report.go b/pkg/compliance/ntia_report.go index a1771c9..75abeb3 100644 --- a/pkg/compliance/ntia_report.go +++ b/pkg/compliance/ntia_report.go @@ -50,7 +50,7 @@ func newNtiaJsonReport() *ntiaComplianceReport { Subtitle: "Part 2: Software Bill of Materials (SBOM)", Revision: "", Run: run{ - Id: uuid.New().String(), + ID: uuid.New().String(), GeneratedAt: time.Now().UTC().Format(time.RFC3339), FileName: "", EngineVersion: "1", @@ -83,19 +83,19 @@ func ntiaJsonReport(db *db, fileName string) { func ntiaConstructSections(db *db) []ntiaSection { var sections []ntiaSection - allIds := db.getAllIds() + allIds := db.getAllIDs() for _, id := range allIds { - records := db.getRecordsById(id) + records := db.getRecordsByID(id) for _, r := range records { - section := ntiaSectionDetails[r.check_key] + section := ntiaSectionDetails[r.checkKey] new_section := ntiaSection{ Title: section.Title, Id: section.Id, DataField: section.DataField, Required: section.Required, } - score := ntiaKeyIdScore(db, r.check_key, r.id) + score := ntiaKeyIdScore(db, r.checkKey, r.id) new_section.Score = score.totalScore() if r.id == "doc" { new_section.ElementId = "sbom" @@ -103,7 +103,7 @@ func ntiaConstructSections(db *db) []ntiaSection { new_section.ElementId = r.id } - new_section.ElementResult = r.check_value + new_section.ElementResult = r.checkValue sections = append(sections, new_section) } diff --git a/pkg/compliance/ntia_score.go b/pkg/compliance/ntia_score.go index a94c5c3..85395b7 100644 --- a/pkg/compliance/ntia_score.go +++ b/pkg/compliance/ntia_score.go @@ -45,7 +45,7 @@ func (r *ntiaScoreResult) totalOptionalScore() float64 { } func ntiaKeyIdScore(db *db, key int, id string) *ntiaScoreResult { - records := db.getRecordsByKeyId(key, id) + records := db.getRecordsByKeyID(key, id) if len(records) == 0 { return newNtiaScoreResult(id) @@ -80,7 +80,7 @@ func ntiaAggregateScore(db *db) *ntiaScoreResult { var results []ntiaScoreResult var finalResult ntiaScoreResult - ids := db.getAllIds() + ids := db.getAllIDs() for _, id := range ids { results = append(results, *ntiaIdScore(db, id)) } @@ -96,7 +96,7 @@ func ntiaAggregateScore(db *db) *ntiaScoreResult { } func ntiaIdScore(db *db, id string) *ntiaScoreResult { - records := db.getRecordsById(id) + records := db.getRecordsByID(id) if len(records) == 0 { return newNtiaScoreResult(id) diff --git a/pkg/compliance/ntia_test.go b/pkg/compliance/ntia_test.go index 17cd694..9e088f5 100644 --- a/pkg/compliance/ntia_test.go +++ b/pkg/compliance/ntia_test.go @@ -146,9 +146,9 @@ func TestNtiaSpdxSbomPass(t *testing.T) { for _, test := range testCases { assert.Equal(t, test.expected.score, test.actual.score) - assert.Equal(t, test.expected.key, test.actual.check_key) + assert.Equal(t, test.expected.key, test.actual.checkKey) assert.Equal(t, test.expected.id, test.actual.id) - assert.Equal(t, test.expected.result, test.actual.check_value) + assert.Equal(t, test.expected.result, test.actual.checkValue) } } @@ -269,9 +269,9 @@ func TestNtiaCdxSbomPass(t *testing.T) { } for _, test := range testCases { assert.Equal(t, test.expected.score, test.actual.score) - assert.Equal(t, test.expected.key, test.actual.check_key) + assert.Equal(t, test.expected.key, test.actual.checkKey) assert.Equal(t, test.expected.id, test.actual.id) - assert.Equal(t, test.expected.result, test.actual.check_value) + assert.Equal(t, test.expected.result, test.actual.checkValue) } } From 709611cdaf32293738fd773e005c1f0bea3c40c3 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Mon, 12 Aug 2024 13:04:43 +0530 Subject: [PATCH 07/19] implement fakeauthor to author interface Signed-off-by: Vivek Kumar Sahu --- pkg/sbom/sbomfakes/fake_author.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/sbom/sbomfakes/fake_author.go b/pkg/sbom/sbomfakes/fake_author.go index d559dcc..6528087 100644 --- a/pkg/sbom/sbomfakes/fake_author.go +++ b/pkg/sbom/sbomfakes/fake_author.go @@ -42,7 +42,7 @@ type FakeAuthor struct { invocationsMutex sync.RWMutex } -func (fake *FakeAuthor) Email() string { +func (fake *FakeAuthor) GetEmail() string { fake.emailMutex.Lock() ret, specificReturn := fake.emailReturnsOnCall[len(fake.emailArgsForCall)] fake.emailArgsForCall = append(fake.emailArgsForCall, struct { @@ -95,7 +95,7 @@ func (fake *FakeAuthor) EmailReturnsOnCall(i int, result1 string) { }{result1} } -func (fake *FakeAuthor) Name() string { +func (fake *FakeAuthor) GetName() string { fake.nameMutex.Lock() ret, specificReturn := fake.nameReturnsOnCall[len(fake.nameArgsForCall)] fake.nameArgsForCall = append(fake.nameArgsForCall, struct { @@ -148,7 +148,7 @@ func (fake *FakeAuthor) NameReturnsOnCall(i int, result1 string) { }{result1} } -func (fake *FakeAuthor) Type() string { +func (fake *FakeAuthor) GetType() string { fake.typeMutex.Lock() ret, specificReturn := fake.typeReturnsOnCall[len(fake.typeArgsForCall)] fake.typeArgsForCall = append(fake.typeArgsForCall, struct { @@ -229,4 +229,4 @@ func (fake *FakeAuthor) recordInvocation(key string, args []interface{}) { fake.invocations[key] = append(fake.invocations[key], args) } -var _ sbom.Author = new(FakeAuthor) +var _ sbom.GetAuthor = new(FakeAuthor) From 302ee12e3aa58c67ac8616820c4e7aa90d337b0d Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Mon, 12 Aug 2024 17:37:50 +0530 Subject: [PATCH 08/19] fix dependencies issue Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/ntia.go | 8 +++----- pkg/sbom/spdx.go | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index fa6cd10..c99b9ac 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -17,6 +17,7 @@ package compliance import ( "context" "fmt" + "strings" "time" "github.com/interlynk-io/sbomqs/pkg/logger" @@ -33,9 +34,6 @@ func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outForm db.addRecord(ntiaSbomCreator(doc)) db.addRecord(ntiaSbomCreatedTimestamp(doc)) db.addRecords(ntiaComponents(doc)) - // db.addRecord(ntiaBuildPhase(doc)) - // db.addRecord(ntiaComponentHash(doc)) - // db.addRecord(ntiaComponentlicense(doc)) if outFormat == "json" { ntiaJsonReport(db, fileName) @@ -320,8 +318,8 @@ func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) * result, score := "", 0.0 if relation := doc.Relations(); relation != nil { for _, rel := range relation { - if rel.GetFrom() != "" && rel.GetTo() != "" { - result = rel.GetFrom() + ", " + rel.GetTo() + if strings.Contains(rel.GetFrom(), component.GetID()) { + result = rel.GetTo() score = 10 return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) } diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index d2012cb..1c84bc1 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -234,7 +234,7 @@ func (s *SpdxDoc) parseComps() { fromRelsPresent := func(rels []GetRelation, id string) bool { for _, r := range rels { - if r.GetFrom() == id { + if strings.Contains(r.GetFrom(), id) { return true } } From a882fc3419780accf84155bdde1cb4204dd602d7 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Mon, 12 Aug 2024 18:21:43 +0530 Subject: [PATCH 09/19] sort section for section wise output Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/ntia_report.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/pkg/compliance/ntia_report.go b/pkg/compliance/ntia_report.go index 75abeb3..7895bd4 100644 --- a/pkg/compliance/ntia_report.go +++ b/pkg/compliance/ntia_report.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "sort" "time" "github.com/google/uuid" @@ -12,16 +13,16 @@ import ( ) var ntiaSectionDetails = map[int]ntiaSection{ - SBOM_MACHINE_FORMAT: {Title: "Automation Support", Id: "2.1", Required: true, DataField: "Machine-Readable Formats"}, - SBOM_CREATOR: {Title: "Required fields sboms ", Id: "1.1", Required: true, DataField: "Author"}, - SBOM_TIMESTAMP: {Title: "Required fields sboms", Id: "1.2", Required: true, DataField: "Timestamp"}, - SBOM_COMPONENTS: {Title: "Required sbom component", Id: "1.4", Required: true, DataField: "Packages"}, - COMP_NAME: {Title: "Required fields components", Id: "1.5", Required: true, DataField: "Package Name"}, - COMP_DEPTH: {Title: "Required fields components", Id: "1.3", Required: true, DataField: "Dependencies on other components"}, - COMP_CREATOR: {Title: "Required fields component", Id: "1.6", Required: true, DataField: "Package Supplier"}, - PACK_SUPPLIER: {Title: "Required fields component", Id: "1.6", Required: true, DataField: "Package Supplier"}, - COMP_VERSION: {Title: "Required fields components", Id: "1.7", Required: true, DataField: "Package Version"}, - COMP_OTHER_UNIQ_IDS: {Title: "Required fields component", Id: "1.8", Required: true, DataField: "Other Uniq IDs"}, + SBOM_MACHINE_FORMAT: {Title: "Automation Support", Id: "1.1", Required: true, DataField: "Machine-Readable Formats"}, + SBOM_CREATOR: {Title: "Required fields sboms ", Id: "2.1", Required: true, DataField: "Author"}, + SBOM_TIMESTAMP: {Title: "Required fields sboms", Id: "2.2", Required: true, DataField: "Timestamp"}, + SBOM_COMPONENTS: {Title: "Required sbom component", Id: "2.3", Required: true, DataField: "Packages"}, + COMP_NAME: {Title: "Required fields components", Id: "2.4", Required: true, DataField: "Package Name"}, + COMP_DEPTH: {Title: "Required fields components", Id: "2.5", Required: true, DataField: "Dependencies on other components"}, + COMP_CREATOR: {Title: "Required fields component", Id: "2.6", Required: true, DataField: "Package Supplier"}, + PACK_SUPPLIER: {Title: "Required fields component", Id: "2.6", Required: true, DataField: "Package Supplier"}, + COMP_VERSION: {Title: "Required fields components", Id: "2.8", Required: true, DataField: "Package Version"}, + COMP_OTHER_UNIQ_IDS: {Title: "Required fields component", Id: "2.9", Required: true, DataField: "Other Uniq IDs"}, } type ntiaSection struct { @@ -46,7 +47,7 @@ type ntiaComplianceReport struct { func newNtiaJsonReport() *ntiaComplianceReport { return &ntiaComplianceReport{ - Name: "NTIA Compliance Report", + Name: "NTIA-minimum elements Compliance Report", Subtitle: "Part 2: Software Bill of Materials (SBOM)", Revision: "", Run: run{ @@ -123,6 +124,15 @@ func ntiaDetailedReport(db *db, fileName string) { table.SetAutoMergeCellsByColumnIndex([]int{0}) sections := ntiaConstructSections(db) + + // Sort sections by ElementId and then by SectionId + sort.Slice(sections, func(i, j int) bool { + if sections[i].ElementId == sections[j].ElementId { + return sections[i].Id < sections[j].Id + } + return sections[i].ElementId < sections[j].ElementId + }) + for _, section := range sections { sectionId := section.Id if !section.Required { From d876dcd545f8f212e060bd426966cff0c64b7d65 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Tue, 13 Aug 2024 13:09:19 +0530 Subject: [PATCH 10/19] improve code readability Signed-off-by: Vivek Kumar Sahu --- Compliance.md | 27 +++++++++++---------- pkg/compliance/ntia.go | 44 +++++++++-------------------------- pkg/compliance/ntia_report.go | 4 ++-- 3 files changed, 26 insertions(+), 49 deletions(-) diff --git a/Compliance.md b/Compliance.md index 6ecd31f..cfab5cb 100644 --- a/Compliance.md +++ b/Compliance.md @@ -67,21 +67,20 @@ The [OpenChain Telco](https://github.com/OpenChain-Project/Reference-Material/bl | Method of SBOM delivery | 3.7 | `SBOM delivery method` | delivery method | | | SBOM Scope | 3.8 | `SBOM scope` | sbom scope | | - ## NTIA minimum elements: SBOM Requirements for NTIA The [NTIA](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2.pdf) specifies mandatory properties for an SBOM. Below is how we have derived all the values. -| NTIA minimum elements | Section ID | NTIA Fields | CycloneDX |SPDX(2.3) | Notes | -| :--- | :--- |:--- | :--- | :--- | :--- | -| Data Fields | 1.1 | Author of the SBOM data | metadata->authors, metadata->supplier | creator | | -| | 1.2 | Timestamp | metadata->timestamp | created | | -| | 1.3 | Dependency Relationship | dependencies, composition | relationships | | -| | 1.4 | present | | | all package elements | -| | 1.5 | Component Name | component->name | package->name | | -| | 1.6 | Supplier Name | component->supplier | packageSupplier, packageOriginator | | -| | 1.7 | Version of Component | component->version | package->version | | -| | 1.8 | Other Uniq IDs | component->cpe, component->purl | DocumentNamespace, SPDXID | | -| Automation Support | 2.1 | Format | BomFormat | SPDXversion | | -| Practices and Processes | 3.1 | Depth | dependencies, compositions | relationships | | -| | 3.2 | Known Unknowns | | | | +| NTIA minimum elements | Section ID | NTIA Fields | CycloneDX |SPDX(2.3) | Notes | +| :--- | :--- |:--- | :--- | :--- | :--- | +| Automation Support | 1.1 | `Machine Readable Format` | BomFormat & data forrmat | SPDXversion & data forrmat | | +| SBOM Data Fields | 2.1 | `Author of the SBOM` | metadata->authors, metadata->supplier | creator | | +| | 2.2 | `Timestamp` | metadata->timestamp | created | | +| | 2.3 | `present` | | | all package elements | +| Package Data Fields | 2.4 | `Package Name` | component->name | package->name | | +| | 2.5 | `Dependency Relationship` | dependencies, composition | relationships | | +| | 2.6 | `Supplier Name` | component->supplier | packageSupplier, packageOriginator | | +| | 2.7 | `Version of Component` | component->version | package->version | | +| | 2.8 | `Other Uniq IDs` | component->cpe, component->purl | DocumentNamespace, SPDXID | | +| Practices and Processes | 3.1 | `Depth` | dependencies, compositions | relationships | | +| | 3.2 | `Known Unknowns` | | | | diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index c99b9ac..f66a500 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -22,6 +22,12 @@ import ( "github.com/interlynk-io/sbomqs/pkg/logger" "github.com/interlynk-io/sbomqs/pkg/sbom" + "github.com/samber/lo" +) + +var ( + validSpec = []string{"cyclonedx", "spdx"} + validFormats = []string{"json", "xml", "yaml", "yml", "tag-value"} ) func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) { @@ -53,7 +59,8 @@ func ntiaAutomationSpec(doc sbom.Document) *record { spec := doc.Spec().GetSpecType() result, score := "", 0.0 - if fileFormat := doc.Spec().FileFormat(); fileFormat == "json" || fileFormat == "tag-value" { + fileFormat := doc.Spec().FileFormat() + if lo.Contains(validFormats, fileFormat) && lo.Contains(validSpec, spec) { result = spec + ", " + fileFormat score = 10.0 } else { @@ -78,7 +85,6 @@ func ntiaSbomCreator(doc sbom.Document) *record { } } } - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } else if spec == "cyclonedx" { @@ -189,7 +195,7 @@ func ntiaComponents(doc sbom.Document) []*record { records := []*record{} if len(doc.Components()) == 0 { - records := append(records, newRecordStmt(SBOM_COMPONENTS, "doc", "", 0.0)) + records = append(records, newRecordStmt(SBOM_COMPONENTS, "SBOM Data Fields", "absent", 0.0)) return records } @@ -309,30 +315,24 @@ func ntiaComponentVersion(component sbom.GetComponent) *record { return newRecordStmt(COMP_VERSION, component.GetID(), "", 0.0) } -// func ntiaComponentDepth(doc sbom.Document) *record {} - func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) *record { spec := doc.Spec().GetSpecType() + result, score := "", 0.0 if spec == "spdx" { - result, score := "", 0.0 if relation := doc.Relations(); relation != nil { for _, rel := range relation { if strings.Contains(rel.GetFrom(), component.GetID()) { result = rel.GetTo() score = 10 - return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) } } } - - return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) - } else if spec == "cyclonedx" { return bsiComponentDepth(component) } - return newRecordStmt(COMP_DEPTH, component.GetID(), "", 0.0) + return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) } func ntiaComponentOtherUniqIds(doc sbom.Document, component sbom.GetComponent) *record { @@ -382,25 +382,3 @@ func ntiaComponentOtherUniqIds(doc sbom.Document, component sbom.GetComponent) * } return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetID(), "", 0.0) } - -// Recommended sbom stuffs -// lifecycle -// func ntiaBuildPhase(doc sbom.Document) *record { -// lifecycles := doc.Lifecycles() -// result, score := "", 0.0 - -// found := lo.Count(lifecycles, "build") - -// if found > 0 { -// result = "build" -// score = 10.0 -// } - -// return newRecordStmt(SBOM_BUILD, "doc", result, score) -// } - -// func ntiaSbomDepth(doc sbom.Document) *record {} - -// Recommended component stuffs -// func ntiaComponentHash(doc sbom.Document) *record {} -// func ntiaComponentlicense(doc sbom.Document) *record {} diff --git a/pkg/compliance/ntia_report.go b/pkg/compliance/ntia_report.go index 7895bd4..7dbdc96 100644 --- a/pkg/compliance/ntia_report.go +++ b/pkg/compliance/ntia_report.go @@ -21,8 +21,8 @@ var ntiaSectionDetails = map[int]ntiaSection{ COMP_DEPTH: {Title: "Required fields components", Id: "2.5", Required: true, DataField: "Dependencies on other components"}, COMP_CREATOR: {Title: "Required fields component", Id: "2.6", Required: true, DataField: "Package Supplier"}, PACK_SUPPLIER: {Title: "Required fields component", Id: "2.6", Required: true, DataField: "Package Supplier"}, - COMP_VERSION: {Title: "Required fields components", Id: "2.8", Required: true, DataField: "Package Version"}, - COMP_OTHER_UNIQ_IDS: {Title: "Required fields component", Id: "2.9", Required: true, DataField: "Other Uniq IDs"}, + COMP_VERSION: {Title: "Required fields components", Id: "2.7", Required: true, DataField: "Package Version"}, + COMP_OTHER_UNIQ_IDS: {Title: "Required fields component", Id: "2.8", Required: true, DataField: "Other Uniq IDs"}, } type ntiaSection struct { From 80053c41d8047e7f3507a7ce9e95393de675eab8 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Tue, 13 Aug 2024 16:17:41 +0530 Subject: [PATCH 11/19] complete test for fail case too Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/ntia_test.go | 452 ++++++++++++------------------------ 1 file changed, 148 insertions(+), 304 deletions(-) diff --git a/pkg/compliance/ntia_test.go b/pkg/compliance/ntia_test.go index 9e088f5..d231caa 100644 --- a/pkg/compliance/ntia_test.go +++ b/pkg/compliance/ntia_test.go @@ -12,6 +12,7 @@ func createSpdxDummyDocumentNtia() sbom.Document { s := sbom.NewSpec() s.Version = "SPDX-2.3" s.SpecType = "spdx" + s.Format = "json" s.CreationTimestamp = "2023-05-04T09:33:40Z" var creators []sbom.GetTool @@ -25,7 +26,7 @@ func createSpdxDummyDocumentNtia() sbom.Document { pack.Name = "core-js" supplier := sbom.Supplier{ - Email: "vivekkumarsahu650@gmail.com", + Email: "hello@interlynk.io", } pack.Supplier = supplier @@ -73,9 +74,9 @@ func TestNtiaSpdxSbomPass(t *testing.T) { actual: ntiaAutomationSpec(doc), expected: desiredNtia{ score: 10.0, - result: "spdx", - key: SBOM_SPEC, - id: "SBOM format", + result: "spdx, json", + key: SBOM_MACHINE_FORMAT, + id: "Automation Support", }, }, { @@ -84,7 +85,7 @@ func TestNtiaSpdxSbomPass(t *testing.T) { score: 10.0, result: "syft", key: SBOM_CREATOR, - id: "doc", + id: "SBOM Data Fields", }, }, { @@ -93,14 +94,14 @@ func TestNtiaSpdxSbomPass(t *testing.T) { score: 10.0, result: "2023-05-04T09:33:40Z", key: SBOM_TIMESTAMP, - id: "doc", + id: "SBOM Data Fields", }, }, { actual: ntiaComponentCreator(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, - result: "vivekkumarsahu650@gmail.com", + result: "hello@interlynk.io", key: PACK_SUPPLIER, id: doc.Components()[0].GetID(), }, @@ -129,7 +130,7 @@ func TestNtiaSpdxSbomPass(t *testing.T) { expected: desiredNtia{ score: 10.0, result: "purl:(1/1)", - key: PACK_EXT_REF, + key: COMP_OTHER_UNIQ_IDS, id: doc.Components()[0].GetID(), }, }, @@ -137,7 +138,7 @@ func TestNtiaSpdxSbomPass(t *testing.T) { actual: ntiaComponentDependencies(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, - result: "SPDXRef-Package-go-module-github.com-abc, SPDXRef-Package-go-module-github.com-xyz", + result: "SPDXRef-Package-go-module-github.com-xyz", key: COMP_DEPTH, id: doc.Components()[0].GetID(), }, @@ -157,10 +158,11 @@ func createCdxDummyDocumentNtia() sbom.Document { cdxSpec.Version = "1.4" cdxSpec.SpecType = "cyclonedx" cdxSpec.CreationTimestamp = "2023-05-04T09:33:40Z" + cdxSpec.Format = "xml" var authors []sbom.GetAuthor author := sbom.Author{ - Email: "vivekkumarsahu650@gmail.com", + Email: "hello@interlynk.io", } authors = append(authors, author) @@ -169,7 +171,7 @@ func createCdxDummyDocumentNtia() sbom.Document { comp.Name = "core-js" supplier := sbom.Supplier{ - Email: "vivekkumarsahu650@gmail.com", + Email: "hello@interlynk.io", } comp.Supplier = supplier @@ -206,18 +208,18 @@ func TestNtiaCdxSbomPass(t *testing.T) { actual: ntiaAutomationSpec(doc), expected: desiredNtia{ score: 10.0, - result: "cyclonedx", - key: SBOM_SPEC, - id: "SBOM format", + result: "cyclonedx, xml", + key: SBOM_MACHINE_FORMAT, + id: "Automation Support", }, }, { actual: ntiaSbomCreator(doc), expected: desiredNtia{ score: 10.0, - result: "vivekkumarsahu650@gmail.com", + result: "hello@interlynk.io", key: SBOM_CREATOR, - id: "doc", + id: "SBOM Data Fields", }, }, { @@ -226,19 +228,18 @@ func TestNtiaCdxSbomPass(t *testing.T) { score: 10.0, result: "2023-05-04T09:33:40Z", key: SBOM_TIMESTAMP, - id: "doc", + id: "SBOM Data Fields", }, }, { actual: ntiaComponentCreator(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, - result: "vivekkumarsahu650@gmail.com", + result: "hello@interlynk.io", key: COMP_CREATOR, id: doc.Components()[0].GetID(), }, }, - { actual: ntiaComponentName(doc.Components()[0]), expected: desiredNtia{ @@ -275,288 +276,131 @@ func TestNtiaCdxSbomPass(t *testing.T) { } } -// func createFailureDummyDocumentNtia() sbom.Document { -// s := sbom.NewSpec() -// s.Version = "" -// s.Format = "xml" -// s.SpecType = "cyclonedx" -// s.Name = "" -// s.Namespace = "" -// s.Organization = "" -// s.CreationTimestamp = "wrong-time-format" -// s.Spdxid = "" -// s.Comment = "" -// lics := licenses.CreateCustomLicense("", "") -// s.Licenses = append(s.Licenses, lics) - -// var tools []sbom.GetTool -// tool := sbom.Tool{ -// Name: "", -// } -// tools = append(tools, tool) - -// pack := sbom.NewComponent() -// pack.Version = "" -// pack.Name = "" -// pack.Spdxid = "" -// pack.CopyRight = "NOASSERTION" -// pack.FileAnalyzed = false -// pack.Id = "" -// pack.PackageLicenseConcluded = "NONE" -// pack.PackageLicenseDeclared = "NOASSERTION" -// pack.DownloadLocation = "" - -// supplier := sbom.Supplier{ -// Email: "", -// } -// pack.Supplier = supplier - -// checksum := sbom.Checksum{ -// Alg: "SHA-1", -// Content: "443238d9cf19f77ccc8cdda3ba5421ea9ea2bc78", -// } - -// var checksums []sbom.GetChecksum -// checksums = append(checksums, checksum) -// pack.Checksums = checksums - -// extRef := sbom.ExternalReference{ -// RefType: "cpe23Type", -// } -// var externalReferences []sbom.GetExternalReference -// externalReferences = append(externalReferences, extRef) -// pack.ExternalRefs = externalReferences - -// var packages []sbom.GetComponent -// packages = append(packages, pack) - -// doc := sbom.SpdxDoc{ -// SpdxSpec: s, -// Comps: packages, -// SpdxTools: tools, -// } -// return doc -// } - -// func TestOctSbomFail(t *testing.T) { -// doc := createFailureDummyDocument() -// testCases := []struct { -// actual *record -// expected desired -// }{ -// { -// actual: octSpec(doc), -// expected: desired{ -// score: 0.0, -// result: "cyclonedx", -// key: SBOM_SPEC, -// id: "SBOM Format", -// }, -// }, -// { -// actual: octSbomName(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_NAME, -// id: "SPDX Elements", -// }, -// }, -// { -// actual: octSbomNamespace(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_NAMESPACE, -// id: "SPDX Elements", -// }, -// }, -// { -// actual: octSbomOrganization(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_ORG, -// id: "SBOM Build Information", -// }, -// }, -// { -// actual: octSbomComment(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_COMMENT, -// id: "SPDX Elements", -// }, -// }, -// { -// actual: octSbomTool(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_TOOL, -// id: "SBOM Build Information", -// }, -// }, -// { -// actual: octSbomLicense(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_LICENSE, -// id: "SPDX Elements", -// }, -// }, -// { -// actual: octSpecVersion(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_SPEC_VERSION, -// id: "SPDX Elements", -// }, -// }, -// { -// actual: octCreatedTimestamp(doc), -// expected: desired{ -// score: 0.0, -// result: "wrong-time-format", -// key: SBOM_TIMESTAMP, -// id: "SPDX Elements", -// }, -// }, -// { -// actual: octSpecSpdxID(doc), -// expected: desired{ -// score: 0.0, -// result: "", -// key: SBOM_SPDXID, -// id: "SPDX Elements", -// }, -// }, -// { -// actual: octMachineFormat(doc), -// expected: desired{ -// score: 0.0, -// result: "cyclonedx, xml", -// key: SBOM_MACHINE_FORMAT, -// id: "Machine Readable Data Format", -// }, -// }, -// { -// actual: octHumanFormat(doc), -// expected: desired{ -// score: 0.0, -// result: "xml", -// key: SBOM_HUMAN_FORMAT, -// id: "Human Readable Data Format", -// }, -// }, -// { -// actual: octPackageName(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "", -// key: PACK_NAME, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageVersion(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "", -// key: PACK_VERSION, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageSpdxID(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "", -// key: PACK_SPDXID, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageSupplier(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "", -// key: PACK_SUPPLIER, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageHash(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "", -// key: PACK_HASH, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageExternalRefs(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "cpe23Type", -// key: PACK_EXT_REF, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageCopyright(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "NOASSERTION", -// key: PACK_COPYRIGHT, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageFileAnalyzed(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "no", -// key: PACK_FILE_ANALYZED, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageConLicense(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "NONE", -// key: PACK_LICENSE_CON, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageDecLicense(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "NOASSERTION", -// key: PACK_LICENSE_DEC, -// id: doc.Components()[0].GetID(), -// }, -// }, -// { -// actual: octPackageDownloadUrl(doc.Components()[0]), -// expected: desired{ -// score: 0.0, -// result: "", -// key: PACK_DOWNLOAD_URL, -// id: doc.Components()[0].GetID(), -// }, -// }, -// } - -// for _, test := range testCases { -// assert.Equal(t, test.expected.score, test.actual.score) -// assert.Equal(t, test.expected.key, test.actual.check_key) -// assert.Equal(t, test.expected.id, test.actual.id) -// assert.Equal(t, test.expected.result, test.actual.check_value) -// } -// } +func createSpdxDummyDocumentFailNtia() sbom.Document { + s := sbom.NewSpec() + s.Version = "SPDX-4.0" + s.SpecType = "swid" + s.Format = "fjson" + s.CreationTimestamp = "2023-05-04" + + var creators []sbom.GetTool + creator := sbom.Tool{ + Name: "", + } + creators = append(creators, creator) + + pack := sbom.NewComponent() + pack.Version = "" + pack.Name = "" + + supplier := sbom.Supplier{ + Email: "", + } + pack.Supplier = supplier + + extRef := sbom.ExternalReference{ + RefType: "purl", + } + + var externalReferences []sbom.GetExternalReference + externalReferences = append(externalReferences, extRef) + pack.ExternalRefs = externalReferences + + var packages []sbom.GetComponent + packages = append(packages, pack) + + depend := sbom.Relation{ + From: "", + To: "", + } + var dependencies []sbom.GetRelation + dependencies = append(dependencies, depend) + + doc := sbom.SpdxDoc{ + SpdxSpec: s, + Comps: packages, + SpdxTools: creators, + Rels: dependencies, + } + return doc +} + +func TestNTIASbomFail(t *testing.T) { + doc := createSpdxDummyDocumentFailNtia() + testCases := []struct { + actual *record + expected desiredNtia + }{ + { + actual: ntiaAutomationSpec(doc), + expected: desiredNtia{ + score: 0.0, + result: "swid, fjson", + key: SBOM_MACHINE_FORMAT, + id: "Automation Support", + }, + }, + { + actual: ntiaSbomCreator(doc), + expected: desiredNtia{ + score: 0.0, + result: "", + key: SBOM_CREATOR, + id: "SBOM Data Fields", + }, + }, + { + actual: ntiaSbomCreatedTimestamp(doc), + expected: desiredNtia{ + score: 0.0, + result: "2023-05-04", + key: SBOM_TIMESTAMP, + id: "SBOM Data Fields", + }, + }, + { + actual: ntiaComponentCreator(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 0.0, + result: "", + key: COMP_CREATOR, + id: doc.Components()[0].GetID(), + }, + }, + + { + actual: ntiaComponentName(doc.Components()[0]), + expected: desiredNtia{ + score: 0.0, + result: "", + key: COMP_NAME, + id: doc.Components()[0].GetID(), + }, + }, + { + actual: ntiaComponentVersion(doc.Components()[0]), + expected: desiredNtia{ + score: 0.0, + result: "", + key: COMP_VERSION, + id: doc.Components()[0].GetID(), + }, + }, + { + actual: ntiaComponentOtherUniqIds(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 0.0, + result: "", + key: COMP_OTHER_UNIQ_IDS, + id: doc.Components()[0].GetID(), + }, + }, + } + + for _, test := range testCases { + assert.Equal(t, test.expected.score, test.actual.score) + assert.Equal(t, test.expected.key, test.actual.checkKey) + assert.Equal(t, test.expected.id, test.actual.id) + assert.Equal(t, test.expected.result, test.actual.checkValue) + } +} From 673a9290d23a7f139a770fbbc260210738f95a87 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Tue, 13 Aug 2024 19:40:11 +0530 Subject: [PATCH 12/19] fix lintings errors Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/ntia.go | 10 ++---- pkg/compliance/ntia_report.go | 62 +++++++++++++++++------------------ pkg/compliance/ntia_score.go | 54 +++++++++++++++--------------- pkg/compliance/ntia_test.go | 6 ++-- 4 files changed, 64 insertions(+), 68 deletions(-) diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index f66a500..da388b4 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -42,7 +42,7 @@ func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outForm db.addRecords(ntiaComponents(doc)) if outFormat == "json" { - ntiaJsonReport(db, fileName) + ntiaJSONReport(db, fileName) } if outFormat == "basic" { @@ -85,9 +85,7 @@ func ntiaSbomCreator(doc sbom.Document) *record { } } } - } else if spec == "cyclonedx" { - for _, author := range doc.Authors() { if author.GetEmail() != "" { result = author.GetEmail() @@ -203,9 +201,8 @@ func ntiaComponents(doc sbom.Document) []*record { records = append(records, ntiaComponentName(component)) records = append(records, ntiaComponentCreator(doc, component)) records = append(records, ntiaComponentVersion(component)) - records = append(records, ntiaComponentOtherUniqIds(doc, component)) + records = append(records, ntiaComponentOtherUniqIDs(doc, component)) records = append(records, ntiaComponentDependencies(doc, component)) - } records = append(records, newRecordStmt(SBOM_COMPONENTS, "SBOM Data Fields", "present", 10.0)) return records @@ -300,7 +297,6 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor } } } - } return newRecordStmt(COMP_CREATOR, component.GetID(), "", 0.0) } @@ -335,7 +331,7 @@ func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) * return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) } -func ntiaComponentOtherUniqIds(doc sbom.Document, component sbom.GetComponent) *record { +func ntiaComponentOtherUniqIDs(doc sbom.Document, component sbom.GetComponent) *record { spec := doc.Spec().GetSpecType() if spec == "spdx" { diff --git a/pkg/compliance/ntia_report.go b/pkg/compliance/ntia_report.go index 7dbdc96..8576a7f 100644 --- a/pkg/compliance/ntia_report.go +++ b/pkg/compliance/ntia_report.go @@ -13,24 +13,24 @@ import ( ) var ntiaSectionDetails = map[int]ntiaSection{ - SBOM_MACHINE_FORMAT: {Title: "Automation Support", Id: "1.1", Required: true, DataField: "Machine-Readable Formats"}, - SBOM_CREATOR: {Title: "Required fields sboms ", Id: "2.1", Required: true, DataField: "Author"}, - SBOM_TIMESTAMP: {Title: "Required fields sboms", Id: "2.2", Required: true, DataField: "Timestamp"}, - SBOM_COMPONENTS: {Title: "Required sbom component", Id: "2.3", Required: true, DataField: "Packages"}, - COMP_NAME: {Title: "Required fields components", Id: "2.4", Required: true, DataField: "Package Name"}, - COMP_DEPTH: {Title: "Required fields components", Id: "2.5", Required: true, DataField: "Dependencies on other components"}, - COMP_CREATOR: {Title: "Required fields component", Id: "2.6", Required: true, DataField: "Package Supplier"}, - PACK_SUPPLIER: {Title: "Required fields component", Id: "2.6", Required: true, DataField: "Package Supplier"}, - COMP_VERSION: {Title: "Required fields components", Id: "2.7", Required: true, DataField: "Package Version"}, - COMP_OTHER_UNIQ_IDS: {Title: "Required fields component", Id: "2.8", Required: true, DataField: "Other Uniq IDs"}, + SBOM_MACHINE_FORMAT: {Title: "Automation Support", ID: "1.1", Required: true, DataField: "Machine-Readable Formats"}, + SBOM_CREATOR: {Title: "Required fields sboms ", ID: "2.1", Required: true, DataField: "Author"}, + SBOM_TIMESTAMP: {Title: "Required fields sboms", ID: "2.2", Required: true, DataField: "Timestamp"}, + SBOM_COMPONENTS: {Title: "Required sbom component", ID: "2.3", Required: true, DataField: "Packages"}, + COMP_NAME: {Title: "Required fields components", ID: "2.4", Required: true, DataField: "Package Name"}, + COMP_DEPTH: {Title: "Required fields components", ID: "2.5", Required: true, DataField: "Dependencies on other components"}, + COMP_CREATOR: {Title: "Required fields component", ID: "2.6", Required: true, DataField: "Package Supplier"}, + PACK_SUPPLIER: {Title: "Required fields component", ID: "2.6", Required: true, DataField: "Package Supplier"}, + COMP_VERSION: {Title: "Required fields components", ID: "2.7", Required: true, DataField: "Package Version"}, + COMP_OTHER_UNIQ_IDS: {Title: "Required fields component", ID: "2.8", Required: true, DataField: "Other Uniq IDs"}, } type ntiaSection struct { Title string `json:"section_title"` - Id string `json:"section_id"` + ID string `json:"section_id"` DataField string `json:"section_data_field"` Required bool `json:"required"` - ElementId string `json:"element_id"` + ElementID string `json:"element_id"` ElementResult string `json:"element_result"` Score float64 `json:"score"` } @@ -45,7 +45,7 @@ type ntiaComplianceReport struct { Sections []ntiaSection `json:"sections"` } -func newNtiaJsonReport() *ntiaComplianceReport { +func newNtiaJSONReport() *ntiaComplianceReport { return &ntiaComplianceReport{ Name: "NTIA-minimum elements Compliance Report", Subtitle: "Part 2: Software Bill of Materials (SBOM)", @@ -64,8 +64,8 @@ func newNtiaJsonReport() *ntiaComplianceReport { } } -func ntiaJsonReport(db *db, fileName string) { - jr := newNtiaJsonReport() +func ntiaJSONReport(db *db, fileName string) { + jr := newNtiaJSONReport() jr.Run.FileName = fileName score := ntiaAggregateScore(db) @@ -84,29 +84,29 @@ func ntiaJsonReport(db *db, fileName string) { func ntiaConstructSections(db *db) []ntiaSection { var sections []ntiaSection - allIds := db.getAllIDs() - for _, id := range allIds { + allIDs := db.getAllIDs() + for _, id := range allIDs { records := db.getRecordsByID(id) for _, r := range records { section := ntiaSectionDetails[r.checkKey] - new_section := ntiaSection{ + newSection := ntiaSection{ Title: section.Title, - Id: section.Id, + ID: section.ID, DataField: section.DataField, Required: section.Required, } - score := ntiaKeyIdScore(db, r.checkKey, r.id) - new_section.Score = score.totalScore() + score := ntiaKeyIDScore(db, r.checkKey, r.id) + newSection.Score = score.totalScore() if r.id == "doc" { - new_section.ElementId = "sbom" + newSection.ElementID = "sbom" } else { - new_section.ElementId = r.id + newSection.ElementID = r.id } - new_section.ElementResult = r.checkValue + newSection.ElementResult = r.checkValue - sections = append(sections, new_section) + sections = append(sections, newSection) } } return sections @@ -127,18 +127,18 @@ func ntiaDetailedReport(db *db, fileName string) { // Sort sections by ElementId and then by SectionId sort.Slice(sections, func(i, j int) bool { - if sections[i].ElementId == sections[j].ElementId { - return sections[i].Id < sections[j].Id + if sections[i].ElementID == sections[j].ElementID { + return sections[i].ID < sections[j].ID } - return sections[i].ElementId < sections[j].ElementId + return sections[i].ElementID < sections[j].ElementID }) for _, section := range sections { - sectionId := section.Id + sectionID := section.ID if !section.Required { - sectionId = sectionId + "*" + sectionID = sectionID + "*" } - table.Append([]string{section.ElementId, sectionId, section.DataField, section.ElementResult, fmt.Sprintf("%0.1f", section.Score)}) + table.Append([]string{section.ElementID, sectionID, section.DataField, section.ElementResult, fmt.Sprintf("%0.1f", section.Score)}) } table.Render() } diff --git a/pkg/compliance/ntia_score.go b/pkg/compliance/ntia_score.go index 85395b7..bfde2ee 100644 --- a/pkg/compliance/ntia_score.go +++ b/pkg/compliance/ntia_score.go @@ -44,35 +44,35 @@ func (r *ntiaScoreResult) totalOptionalScore() float64 { return r.optionalScore / float64(r.optionalRecords) } -func ntiaKeyIdScore(db *db, key int, id string) *ntiaScoreResult { +func ntiaKeyIDScore(db *db, key int, id string) *ntiaScoreResult { records := db.getRecordsByKeyID(key, id) if len(records) == 0 { return newNtiaScoreResult(id) } - required_score := 0.0 - optional_score := 0.0 + requiredScore := 0.0 + optionalScore := 0.0 - required_recs := 0 - optional_recs := 0 + requiredRecs := 0 + optionalRecs := 0 for _, r := range records { if r.required { - required_score += r.score - required_recs += 1 + requiredScore += r.score + requiredRecs++ } else { - optional_score += r.score - optional_recs += 1 + optionalScore += r.score + optionalRecs++ } } return &ntiaScoreResult{ id: id, - requiredScore: required_score, - optionalScore: optional_score, - requiredRecords: required_recs, - optionalRecords: optional_recs, + requiredScore: requiredScore, + optionalScore: optionalScore, + requiredRecords: requiredRecs, + optionalRecords: optionalRecs, } } @@ -82,7 +82,7 @@ func ntiaAggregateScore(db *db) *ntiaScoreResult { ids := db.getAllIDs() for _, id := range ids { - results = append(results, *ntiaIdScore(db, id)) + results = append(results, *ntiaIDScore(db, id)) } for _, r := range results { @@ -95,34 +95,34 @@ func ntiaAggregateScore(db *db) *ntiaScoreResult { return &finalResult } -func ntiaIdScore(db *db, id string) *ntiaScoreResult { +func ntiaIDScore(db *db, id string) *ntiaScoreResult { records := db.getRecordsByID(id) if len(records) == 0 { return newNtiaScoreResult(id) } - required_score := 0.0 - optional_score := 0.0 + requiredScore := 0.0 + optionalScore := 0.0 - required_recs := 0 - optional_recs := 0 + requiredRecs := 0 + optionalRecs := 0 for _, r := range records { if r.required { - required_score += r.score - required_recs += 1 + requiredScore += r.score + requiredRecs++ } else { - optional_score += r.score - optional_recs += 1 + optionalScore += r.score + optionalRecs++ } } return &ntiaScoreResult{ id: id, - requiredScore: required_score, - optionalScore: optional_score, - requiredRecords: required_recs, - optionalRecords: optional_recs, + requiredScore: requiredScore, + optionalScore: optionalScore, + requiredRecords: requiredRecs, + optionalRecords: optionalRecs, } } diff --git a/pkg/compliance/ntia_test.go b/pkg/compliance/ntia_test.go index d231caa..208e55c 100644 --- a/pkg/compliance/ntia_test.go +++ b/pkg/compliance/ntia_test.go @@ -126,7 +126,7 @@ func TestNtiaSpdxSbomPass(t *testing.T) { }, }, { - actual: ntiaComponentOtherUniqIds(doc, doc.Components()[0]), + actual: ntiaComponentOtherUniqIDs(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, result: "purl:(1/1)", @@ -259,7 +259,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, }, { - actual: ntiaComponentOtherUniqIds(doc, doc.Components()[0]), + actual: ntiaComponentOtherUniqIDs(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, result: "vivek", @@ -387,7 +387,7 @@ func TestNTIASbomFail(t *testing.T) { }, }, { - actual: ntiaComponentOtherUniqIds(doc, doc.Components()[0]), + actual: ntiaComponentOtherUniqIDs(doc, doc.Components()[0]), expected: desiredNtia{ score: 0.0, result: "", From 45d2e3058d42f6b2ceeff5c3ae38467b9cdb0879 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Fri, 6 Sep 2024 01:32:18 +0530 Subject: [PATCH 13/19] correct dependencies for spdx Signed-off-by: Vivek Kumar Sahu --- Compliance.md | 21 +++--- pkg/compliance/bsi.go | 1 + pkg/compliance/ntia.go | 118 +++++++++++++++++++++++++--------- pkg/compliance/ntia_report.go | 2 +- pkg/sbom/spdx.go | 48 ++++++++++---- 5 files changed, 135 insertions(+), 55 deletions(-) diff --git a/Compliance.md b/Compliance.md index cfab5cb..026ed26 100644 --- a/Compliance.md +++ b/Compliance.md @@ -73,14 +73,13 @@ The [NTIA](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Tech | NTIA minimum elements | Section ID | NTIA Fields | CycloneDX |SPDX(2.3) | Notes | | :--- | :--- |:--- | :--- | :--- | :--- | -| Automation Support | 1.1 | `Machine Readable Format` | BomFormat & data forrmat | SPDXversion & data forrmat | | -| SBOM Data Fields | 2.1 | `Author of the SBOM` | metadata->authors, metadata->supplier | creator | | -| | 2.2 | `Timestamp` | metadata->timestamp | created | | -| | 2.3 | `present` | | | all package elements | -| Package Data Fields | 2.4 | `Package Name` | component->name | package->name | | -| | 2.5 | `Dependency Relationship` | dependencies, composition | relationships | | -| | 2.6 | `Supplier Name` | component->supplier | packageSupplier, packageOriginator | | -| | 2.7 | `Version of Component` | component->version | package->version | | -| | 2.8 | `Other Uniq IDs` | component->cpe, component->purl | DocumentNamespace, SPDXID | | -| Practices and Processes | 3.1 | `Depth` | dependencies, compositions | relationships | | -| | 3.2 | `Known Unknowns` | | | | +| Automation Support | 1.1 | `Machine Readable Format` | BomFormat & data forrmat | SPDXversion & data forrmat | optional | +| SBOM Data Fields | 2.1 | `SBOM Authors` | metadata->authors, metadata->supplier | creator->Person, creator->organization or creator->tool | Mandatory | +| | 2.2 | `SBOM Timestamp` | metadata->timestamp | created | Mandatory | +| Package Data Fields | 2.4 | `Component Name` | component->name | package->name | Mandatory | +| | 2.5 | `SBOM Dependencies` | dependencies | relationships | Mandatory | +| | 2.6 | `Component Supplier Name` | component->supplier | packageSupplier, packageOriginator | Mandatory | +| | 2.7 | `Component Version` | component->version | package->version | Mandatory | +| | 2.8 | `Component with Uniq IDs` | component->cpe, component->purl | externalRef->cpe, externalRef->purl | Mandatory | +| Practices and Processes | 3.1 | `Depth` | dependencies, compositions | relationships | optional | +| | 3.2 | `Known Unknowns` | | | optional | diff --git a/pkg/compliance/bsi.go b/pkg/compliance/bsi.go index 1a6cfec..5ec6d12 100644 --- a/pkg/compliance/bsi.go +++ b/pkg/compliance/bsi.go @@ -67,6 +67,7 @@ const ( PACK_COPYRIGHT COMP_DEPTH SBOM_MACHINE_FORMAT + SBOM_DEPENDENCY SBOM_HUMAN_FORMAT SBOM_BUILD_INFO SBOM_DELIVERY_TIME diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index da388b4..f053868 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -39,6 +39,7 @@ func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outForm db.addRecord(ntiaAutomationSpec(doc)) db.addRecord(ntiaSbomCreator(doc)) db.addRecord(ntiaSbomCreatedTimestamp(doc)) + db.addRecord(ntiaSBOMDependency(doc)) db.addRecords(ntiaComponents(doc)) if outFormat == "json" { @@ -69,13 +70,25 @@ func ntiaAutomationSpec(doc sbom.Document) *record { return newRecordStmt(SBOM_MACHINE_FORMAT, "Automation Support", result, score) } +func ntiaSBOMDependency(doc sbom.Document) *record { + result, score := "", 0.0 + + withDependencies := len(doc.Relations()) + if withDependencies > 0 { + score = 10.0 + } + result = fmt.Sprintf("doc has %d depedencies", withDependencies) + + return newRecordStmt(SBOM_DEPENDENCY, "SBOM Data Fields", result, score) +} + // Required Sbom stuffs func ntiaSbomCreator(doc sbom.Document) *record { spec := doc.Spec().GetSpecType() result, score := "", 0.0 if spec == "spdx" { - name := "" + name, email := "", "" if tools := doc.Tools(); tools != nil { for _, tool := range tools { if name = tool.GetName(); name != "" { @@ -85,6 +98,19 @@ func ntiaSbomCreator(doc sbom.Document) *record { } } } + if authors := doc.Authors(); authors != nil { + for _, author := range authors { + if name = author.GetName(); name != "" { + result = name + score = 10.0 + break + } else if email = author.GetEmail(); email != "" { + result = name + score = 10.0 + break + } + } + } } else if spec == "cyclonedx" { for _, author := range doc.Authors() { if author.GetEmail() != "" { @@ -98,6 +124,18 @@ func ntiaSbomCreator(doc sbom.Document) *record { return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } + tools := doc.Tools() + + if tools != nil { + for _, tool := range tools { + if name := tool.GetName(); name != "" { + result = name + score = 10.0 + break + } + } + } + supplier := doc.Supplier() if supplier != nil { @@ -188,6 +226,17 @@ func ntiaSbomCreatedTimestamp(doc sbom.Document) *record { return newRecordStmt(SBOM_TIMESTAMP, "SBOM Data Fields", result, score) } +var CompIDWithName = make(map[string]string) + +func extractName(comp string) string { + for x, y := range CompIDWithName { + if strings.Contains(comp, x) { + return y + } + } + return "" +} + // Required component stuffs func ntiaComponents(doc sbom.Document) []*record { records := []*record{} @@ -197,6 +246,11 @@ func ntiaComponents(doc sbom.Document) []*record { return records } + // map package ID to Package Name + for _, component := range doc.Components() { + CompIDWithName[component.GetID()] = component.GetName() + } + for _, component := range doc.Components() { records = append(records, ntiaComponentName(component)) records = append(records, ntiaComponentCreator(doc, component)) @@ -204,15 +258,14 @@ func ntiaComponents(doc sbom.Document) []*record { records = append(records, ntiaComponentOtherUniqIDs(doc, component)) records = append(records, ntiaComponentDependencies(doc, component)) } - records = append(records, newRecordStmt(SBOM_COMPONENTS, "SBOM Data Fields", "present", 10.0)) return records } func ntiaComponentName(component sbom.GetComponent) *record { if result := component.GetName(); result != "" { - return newRecordStmt(COMP_NAME, component.GetID(), result, 10.0) + return newRecordStmt(COMP_NAME, component.GetName(), result, 10.0) } - return newRecordStmt(COMP_NAME, component.GetID(), "", 0.0) + return newRecordStmt(COMP_NAME, component.GetName(), "", 0.0) } func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *record { @@ -220,9 +273,8 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor if spec == "spdx" { if supplier := component.Suppliers().GetEmail(); supplier != "" { - return newRecordStmt(PACK_SUPPLIER, component.GetID(), supplier, 10.0) + return newRecordStmt(PACK_SUPPLIER, component.GetName(), supplier, 10.0) } - return newRecordStmt(PACK_SUPPLIER, component.GetID(), "", 0.0) } else if spec == "cyclonedx" { result := "" score := 0.0 @@ -235,7 +287,7 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if supplier.GetURL() != "" { @@ -244,7 +296,7 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if supplier.GetContacts() != nil { @@ -257,7 +309,7 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } } } @@ -271,7 +323,7 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if manufacturer.GetEmail() != "" { @@ -280,7 +332,7 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if manufacturer.GetContacts() != nil { @@ -293,42 +345,46 @@ func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *recor } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } } } } - return newRecordStmt(COMP_CREATOR, component.GetID(), "", 0.0) + return newRecordStmt(COMP_CREATOR, component.GetName(), "", 0.0) } func ntiaComponentVersion(component sbom.GetComponent) *record { result := component.GetVersion() if result != "" { - return newRecordStmt(COMP_VERSION, component.GetID(), result, 10.0) + return newRecordStmt(COMP_VERSION, component.GetName(), result, 10.0) } - return newRecordStmt(COMP_VERSION, component.GetID(), "", 0.0) + return newRecordStmt(COMP_VERSION, component.GetName(), "", 0.0) } func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) *record { - spec := doc.Spec().GetSpecType() result, score := "", 0.0 + var results []string - if spec == "spdx" { - if relation := doc.Relations(); relation != nil { - for _, rel := range relation { - if strings.Contains(rel.GetFrom(), component.GetID()) { - result = rel.GetTo() - score = 10 - } + if relation := doc.Relations(); relation != nil { + for _, rel := range relation { + if strings.Contains(rel.GetFrom(), component.GetID()) { + componentName := extractName(rel.GetTo()) + results = append(results, componentName) + score = 10.0 } } - } else if spec == "cyclonedx" { - return bsiComponentDepth(component) + } + if results != nil { + for _, name := range results { + result += name + ", " + } + } else { + result += "No Dependencies" } - return newRecordStmt(COMP_DEPTH, component.GetID(), result, score) + return newRecordStmt(COMP_DEPTH, component.GetName(), result, score) } func ntiaComponentOtherUniqIDs(doc sbom.Document, component sbom.GetComponent) *record { @@ -351,7 +407,7 @@ func ntiaComponentOtherUniqIDs(doc sbom.Document, component sbom.GetComponent) * x := fmt.Sprintf(":(%d/%d)", containPurlElement, totalElements) result = result + x } - return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) } else if spec == "cyclonedx" { result := "" score := 0.0 @@ -362,7 +418,7 @@ func ntiaComponentOtherUniqIDs(doc sbom.Document, component sbom.GetComponent) * result = string(purl[0]) score = 10.0 - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) } cpes := component.GetCpes() @@ -371,10 +427,10 @@ func ntiaComponentOtherUniqIDs(doc sbom.Document, component sbom.GetComponent) * result = string(cpes[0]) score = 10.0 - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) } - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), "", 0.0) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), "", 0.0) } - return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetID(), "", 0.0) + return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetName(), "", 0.0) } diff --git a/pkg/compliance/ntia_report.go b/pkg/compliance/ntia_report.go index 8576a7f..04321bc 100644 --- a/pkg/compliance/ntia_report.go +++ b/pkg/compliance/ntia_report.go @@ -16,7 +16,7 @@ var ntiaSectionDetails = map[int]ntiaSection{ SBOM_MACHINE_FORMAT: {Title: "Automation Support", ID: "1.1", Required: true, DataField: "Machine-Readable Formats"}, SBOM_CREATOR: {Title: "Required fields sboms ", ID: "2.1", Required: true, DataField: "Author"}, SBOM_TIMESTAMP: {Title: "Required fields sboms", ID: "2.2", Required: true, DataField: "Timestamp"}, - SBOM_COMPONENTS: {Title: "Required sbom component", ID: "2.3", Required: true, DataField: "Packages"}, + SBOM_DEPENDENCY: {Title: "Required fields sboms", ID: "2.3", Required: true, DataField: "Dependencies"}, COMP_NAME: {Title: "Required fields components", ID: "2.4", Required: true, DataField: "Package Name"}, COMP_DEPTH: {Title: "Required fields components", ID: "2.5", Required: true, DataField: "Dependencies on other components"}, COMP_CREATOR: {Title: "Required fields component", ID: "2.6", Required: true, DataField: "Package Supplier"}, diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index 1c84bc1..303831c 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -277,28 +277,52 @@ func (s *SpdxDoc) parseRels() { var err error var aBytes, bBytes []byte + var primaryComponent string for _, r := range s.doc.Relationships { - nr := Relation{} - switch strings.ToUpper(r.Relationship) { - case spdx_common.TypeRelationshipDescribe: - fallthrough - case spdx_common.TypeRelationshipContains: - fallthrough - case spdx_common.TypeRelationshipDependsOn: - aBytes, err = r.RefA.MarshalJSON() + if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDescribe { + bBytes, err = r.RefB.ElementRefID.MarshalJSON() if err != nil { continue } + primaryComponent = string(bBytes) + } + } + // If no primary component found, return early + if primaryComponent == "" { + return + } - bBytes, err = r.RefB.MarshalJSON() + for _, r := range s.doc.Relationships { + if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDependsOn { + aBytes, err = r.RefA.MarshalJSON() + if err != nil { + continue + } + bBytes, err = r.RefB.ElementRefID.MarshalJSON() if err != nil { continue } - nr.From = string(aBytes) - nr.To = string(bBytes) - s.Rels = append(s.Rels, nr) + if string(aBytes) == primaryComponent { + bBytes, err = r.RefB.MarshalJSON() + if err != nil { + continue + } + + nr := Relation{ + From: primaryComponent, + To: string(bBytes), + } + + s.Rels = append(s.Rels, nr) + } else { + nr := Relation{ + From: string(aBytes), + To: string(bBytes), + } + s.Rels = append(s.Rels, nr) + } } } } From 80ae86f92fe77b15ab29b0efe82e7718b41c9db5 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Fri, 6 Sep 2024 02:26:34 +0530 Subject: [PATCH 14/19] add primary component properties Signed-off-by: Vivek Kumar Sahu --- Compliance.md | 3 +- pkg/compliance/bsi.go | 2 +- pkg/compliance/ntia.go | 7 ++-- pkg/sbom/cdx.go | 72 ++++++++++++++++++++--------------------- pkg/sbom/document.go | 4 +-- pkg/sbom/primarycomp.go | 39 ++++++++++++++++++++++ pkg/sbom/spdx.go | 63 +++++++++++++----------------------- pkg/scorer/ntia.go | 6 ++-- pkg/scorer/quality.go | 2 +- 9 files changed, 111 insertions(+), 87 deletions(-) create mode 100644 pkg/sbom/primarycomp.go diff --git a/Compliance.md b/Compliance.md index 026ed26..51c491b 100644 --- a/Compliance.md +++ b/Compliance.md @@ -76,8 +76,9 @@ The [NTIA](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Tech | Automation Support | 1.1 | `Machine Readable Format` | BomFormat & data forrmat | SPDXversion & data forrmat | optional | | SBOM Data Fields | 2.1 | `SBOM Authors` | metadata->authors, metadata->supplier | creator->Person, creator->organization or creator->tool | Mandatory | | | 2.2 | `SBOM Timestamp` | metadata->timestamp | created | Mandatory | +| | 2.3 | `SBOM Dependencies` | dependencies | relationships | Mandatory(number of dependencies primary comp have) | | Package Data Fields | 2.4 | `Component Name` | component->name | package->name | Mandatory | -| | 2.5 | `SBOM Dependencies` | dependencies | relationships | Mandatory | +| | 2.3 | `Component Dependencies` | dependencies | relationships | Optional(Component to component dependencies) | | | 2.6 | `Component Supplier Name` | component->supplier | packageSupplier, packageOriginator | Mandatory | | | 2.7 | `Component Version` | component->version | package->version | Mandatory | | | 2.8 | `Component with Uniq IDs` | component->cpe, component->purl | externalRef->cpe, externalRef->purl | Mandatory | diff --git a/pkg/compliance/bsi.go b/pkg/compliance/bsi.go index 5ec6d12..9de3521 100644 --- a/pkg/compliance/bsi.go +++ b/pkg/compliance/bsi.go @@ -161,7 +161,7 @@ func bsiBuildPhase(doc sbom.Document) *record { } func bsiSbomDepth(doc sbom.Document) *record { - if !doc.PrimaryComponent() { + if !doc.PrimaryComp().Present() { return newRecordStmt(SBOM_DEPTH, "doc", "no-primary", 0.0) } diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index f053868..22eeea4 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -72,12 +72,13 @@ func ntiaAutomationSpec(doc sbom.Document) *record { func ntiaSBOMDependency(doc sbom.Document) *record { result, score := "", 0.0 + // for doc.Components() + totalDependencies := doc.PrimaryComp().Dependencies() - withDependencies := len(doc.Relations()) - if withDependencies > 0 { + if totalDependencies > 0 { score = 10.0 } - result = fmt.Sprintf("doc has %d depedencies", withDependencies) + result = fmt.Sprintf("doc has %d depedencies", totalDependencies) return newRecordStmt(SBOM_DEPENDENCY, "SBOM Data Fields", result, score) } diff --git a/pkg/sbom/cdx.go b/pkg/sbom/cdx.go index cd7a13d..fd49988 100644 --- a/pkg/sbom/cdx.go +++ b/pkg/sbom/cdx.go @@ -36,21 +36,20 @@ var ( ) type CdxDoc struct { - doc *cydx.BOM - format FileFormat - ctx context.Context - CdxSpec *Specs - Comps []GetComponent - CdxAuthors []GetAuthor - CdxTools []GetTool - rels []GetRelation - logs []string - primaryComponent bool - lifecycles []string - supplier GetSupplier - manufacturer Manufacturer - primaryComponentID string - compositions map[string]string + doc *cydx.BOM + format FileFormat + ctx context.Context + CdxSpec *Specs + Comps []GetComponent + CdxAuthors []GetAuthor + CdxTools []GetTool + rels []GetRelation + logs []string + lifecycles []string + supplier GetSupplier + manufacturer Manufacturer + compositions map[string]string + primaryComp primaryComp } func newCDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Document, error) { @@ -90,6 +89,10 @@ func newCDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Documen return doc, err } +func (c CdxDoc) PrimaryComp() PrimaryComp { + return &c.primaryComp +} + func (c CdxDoc) Spec() Spec { return *c.CdxSpec } @@ -114,10 +117,6 @@ func (c CdxDoc) Logs() []string { return c.logs } -func (c CdxDoc) PrimaryComponent() bool { - return c.primaryComponent -} - func (c CdxDoc) Lifecycles() []string { return c.lifecycles } @@ -137,9 +136,8 @@ func (c *CdxDoc) parse() { c.parseSupplier() c.parseManufacturer() c.parseTool() - c.parsePrimaryComponent() c.parseCompositions() - c.parseRels() + c.parseRelsAndPrimaryComp() c.parseComps() } @@ -286,7 +284,7 @@ func copyC(cdxc *cydx.Component, c *CdxDoc) *Component { } } - if cdxc.BOMRef == c.primaryComponentID { + if cdxc.BOMRef == c.primaryComp.id { nc.isPrimary = true } @@ -547,17 +545,32 @@ func (c *CdxDoc) parseManufacturer() { c.manufacturer = m } -func (c *CdxDoc) parseRels() { +func (c *CdxDoc) parseRelsAndPrimaryComp() { + if c.doc.Metadata == nil { + return + } + + if c.doc.Metadata.Component == nil { + return + } + c.primaryComp.present = true + c.primaryComp.id = c.doc.Metadata.Component.BOMRef + var totalDependencies int + c.rels = []GetRelation{} for _, r := range lo.FromPtr(c.doc.Dependencies) { for _, d := range lo.FromPtr(r.Dependencies) { nr := Relation{} nr.From = r.Ref + if r.Ref == c.primaryComp.id { + totalDependencies++ + } nr.To = d c.rels = append(c.rels, nr) } } + c.primaryComp.dependecies = totalDependencies } func (c *CdxDoc) assignSupplier(comp *cydx.Component) *Supplier { @@ -588,19 +601,6 @@ func (c *CdxDoc) assignSupplier(comp *cydx.Component) *Supplier { return &supplier } -func (c *CdxDoc) parsePrimaryComponent() { - if c.doc.Metadata == nil { - return - } - - if c.doc.Metadata.Component == nil { - return - } - - c.primaryComponent = true - c.primaryComponentID = c.doc.Metadata.Component.BOMRef -} - func (c *CdxDoc) parseCompositions() { if c.doc.Compositions == nil { c.compositions = map[string]string{} diff --git a/pkg/sbom/document.go b/pkg/sbom/document.go index c37f292..a47b062 100644 --- a/pkg/sbom/document.go +++ b/pkg/sbom/document.go @@ -25,9 +25,9 @@ type Document interface { Tools() []GetTool Logs() []string - PrimaryComponent() bool - Lifecycles() []string Manufacturer() Manufacturer Supplier() GetSupplier + + PrimaryComp() PrimaryComp } diff --git a/pkg/sbom/primarycomp.go b/pkg/sbom/primarycomp.go new file mode 100644 index 0000000..e1df1b8 --- /dev/null +++ b/pkg/sbom/primarycomp.go @@ -0,0 +1,39 @@ +// Copyright 2024 Interlynk.io +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +type PrimaryComp interface { + Present() bool + ID() string + Dependencies() int +} + +type primaryComp struct { + present bool + id string + dependecies int +} + +func (pc *primaryComp) Present() bool { + return pc.present +} + +func (pc *primaryComp) ID() string { + return pc.id +} + +func (pc *primaryComp) Dependencies() int { + return pc.dependecies +} diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index 303831c..557e164 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -43,18 +43,17 @@ var ( ) type SpdxDoc struct { - doc *spdx.Document - format FileFormat - ctx context.Context - SpdxSpec *Specs - Comps []GetComponent - authors []GetAuthor - SpdxTools []GetTool - Rels []GetRelation - logs []string - primaryComponent bool - primaryComponentID string - lifecycles string + doc *spdx.Document + format FileFormat + ctx context.Context + SpdxSpec *Specs + Comps []GetComponent + authors []GetAuthor + SpdxTools []GetTool + Rels []GetRelation + logs []string + primaryComp primaryComp + lifecycles string } func newSPDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Document, error) { @@ -96,6 +95,10 @@ func newSPDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Docume return doc, err } +func (c SpdxDoc) PrimaryComp() PrimaryComp { + return &c.primaryComp +} + func (s SpdxDoc) Spec() Spec { return *s.SpdxSpec } @@ -120,10 +123,6 @@ func (s SpdxDoc) Logs() []string { return s.logs } -func (s SpdxDoc) PrimaryComponent() bool { - return s.primaryComponent -} - func (s SpdxDoc) Lifecycles() []string { return []string{s.lifecycles} } @@ -140,8 +139,7 @@ func (s *SpdxDoc) parse() { s.parseSpec() s.parseAuthors() s.parseTool() - s.parseRels() - s.parsePrimaryComponent() + s.parseRelsAndPrimaryComp() s.parseComps() } @@ -230,7 +228,7 @@ func (s *SpdxDoc) parseComps() { nc.DownloadLocation = sc.PackageDownloadLocation } - nc.isPrimary = s.primaryComponentID == string(sc.PackageSPDXIdentifier) + nc.isPrimary = s.primaryComp.id == string(sc.PackageSPDXIdentifier) fromRelsPresent := func(rels []GetRelation, id string) bool { for _, r := range rels { @@ -272,12 +270,12 @@ func (s *SpdxDoc) parseAuthors() { } } -func (s *SpdxDoc) parseRels() { +func (s *SpdxDoc) parseRelsAndPrimaryComp() { s.Rels = []GetRelation{} - var err error var aBytes, bBytes []byte var primaryComponent string + var totalDependencies int for _, r := range s.doc.Relationships { if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDescribe { @@ -286,6 +284,8 @@ func (s *SpdxDoc) parseRels() { continue } primaryComponent = string(bBytes) + s.primaryComp.id = primaryComponent + s.primaryComp.present = true } } // If no primary component found, return early @@ -314,6 +314,7 @@ func (s *SpdxDoc) parseRels() { From: primaryComponent, To: string(bBytes), } + totalDependencies++ s.Rels = append(s.Rels, nr) } else { @@ -325,6 +326,7 @@ func (s *SpdxDoc) parseRels() { } } } + s.primaryComp.dependecies = totalDependencies } // creationInfo.Creators.Tool @@ -641,25 +643,6 @@ func (s *SpdxDoc) addSupplierName(index int) string { return "" } -func (s *SpdxDoc) parsePrimaryComponent() { - pkgIDs := make(map[string]*spdx.Package) - - for _, pkg := range s.doc.Packages { - pkgIDs[string(pkg.PackageSPDXIdentifier)] = pkg - } - - for _, r := range s.doc.Relationships { - if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDescribe { - _, ok := pkgIDs[string(r.RefB.ElementRefID)] - if ok { - s.primaryComponentID = string(r.RefB.ElementRefID) - s.primaryComponent = true - return - } - } - } -} - type entity struct { name string email string diff --git a/pkg/scorer/ntia.go b/pkg/scorer/ntia.go index ef1024a..ab46498 100644 --- a/pkg/scorer/ntia.go +++ b/pkg/scorer/ntia.go @@ -116,11 +116,11 @@ func compWithUniqIDCheck(d sbom.Document, c *check) score { func docWithDepedenciesCheck(d sbom.Document, c *check) score { s := newScoreFromCheck(c) - withRelations := len(d.Relations()) - if withRelations > 0 { + totalDependencies := d.PrimaryComp().Dependencies() + if totalDependencies > 0 { s.setScore(10.0) } - s.setDesc(fmt.Sprintf("doc has %d relationships ", withRelations)) + s.setDesc(fmt.Sprintf("doc has %d dependencies ", totalDependencies)) return *s } diff --git a/pkg/scorer/quality.go b/pkg/scorer/quality.go index 3d1d3bf..6a5af5b 100644 --- a/pkg/scorer/quality.go +++ b/pkg/scorer/quality.go @@ -222,7 +222,7 @@ func docWithCreatorCheck(d sbom.Document, c *check) score { func docWithPrimaryComponentCheck(d sbom.Document, c *check) score { s := newScoreFromCheck(c) - if d.PrimaryComponent() { + if d.PrimaryComp().Present() { s.setScore(10.0) s.setDesc("primary component found") return *s From ba04e91a6de822c453b5edff21c7f41008df0a17 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Mon, 9 Sep 2024 14:31:06 +0530 Subject: [PATCH 15/19] resolve liniting errors Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/ntia.go | 50 +++++++++++++++++++----------------------- pkg/sbom/spdx.go | 1 + 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index 22eeea4..9c87ec5 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -127,13 +127,11 @@ func ntiaSbomCreator(doc sbom.Document) *record { tools := doc.Tools() - if tools != nil { - for _, tool := range tools { - if name := tool.GetName(); name != "" { - result = name - score = 10.0 - break - } + for _, tool := range tools { + if name := tool.GetName(); name != "" { + result = name + score = 10.0 + break } } @@ -158,19 +156,18 @@ func ntiaSbomCreator(doc sbom.Document) *record { return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } - if supplier.GetContacts() != nil { - for _, contact := range supplier.GetContacts() { - if contact.Email() != "" { - result = contact.Email() - score = 10.0 - break - } + for _, contact := range supplier.GetContacts() { + if contact.Email() != "" { + result = contact.Email() + score = 10.0 + break } + } - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) - } + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } + } manufacturer := doc.Manufacturer() @@ -194,19 +191,18 @@ func ntiaSbomCreator(doc sbom.Document) *record { return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } - if manufacturer.GetContacts() != nil { - for _, contact := range manufacturer.GetContacts() { - if contact.Email() != "" { - result = contact.Email() - score = 10.0 - break - } + for _, contact := range manufacturer.GetContacts() { + if contact.Email() != "" { + result = contact.Email() + score = 10.0 + break } + } - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) - } + if result != "" { + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } + } } return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index 3c851e3..61aceb4 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -631,6 +631,7 @@ func (s *SpdxDoc) getSupplier(index int) *Supplier { } } +// nolint // https://github.com/spdx/ntia-conformance-checker/issues/100 // Add spdx support to check both supplier and originator func (s *SpdxDoc) addSupplierName(index int) string { From 0f8f22f26916e0ce8ae66003fbf40769e12e9ca8 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Mon, 9 Sep 2024 16:46:04 +0530 Subject: [PATCH 16/19] replace id by name Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/bsi.go | 54 +++++++++++++++++++++--------------------- pkg/compliance/ntia.go | 4 +--- pkg/compliance/oct.go | 40 +++++++++++++++---------------- 3 files changed, 48 insertions(+), 50 deletions(-) diff --git a/pkg/compliance/bsi.go b/pkg/compliance/bsi.go index 9de3521..fded9a0 100644 --- a/pkg/compliance/bsi.go +++ b/pkg/compliance/bsi.go @@ -327,18 +327,18 @@ func bsiComponents(doc sbom.Document) []*record { func bsiComponentDepth(component sbom.GetComponent) *record { if !component.HasRelationShips() { - return newRecordStmt(COMP_DEPTH, component.GetID(), "no-relationships", 0.0) + return newRecordStmt(COMP_DEPTH, component.GetName(), "no-relationships", 0.0) } if component.RelationShipState() == "complete" { - return newRecordStmt(COMP_DEPTH, component.GetID(), "complete", 10.0) + return newRecordStmt(COMP_DEPTH, component.GetName(), "complete", 10.0) } if component.HasRelationShips() { - return newRecordStmt(COMP_DEPTH, component.GetID(), "unattested-has-relationships", 5.0) + return newRecordStmt(COMP_DEPTH, component.GetName(), "unattested-has-relationships", 5.0) } - return newRecordStmt(COMP_DEPTH, component.GetID(), "non-compliant", 0.0) + return newRecordStmt(COMP_DEPTH, component.GetName(), "non-compliant", 0.0) } func bsiComponentLicense(component sbom.GetComponent) *record { @@ -347,7 +347,7 @@ func bsiComponentLicense(component sbom.GetComponent) *record { if len(licenses) == 0 { // fmt.Printf("component %s : %s has no licenses\n", component.Name(), component.Version()) - return newRecordStmt(COMP_LICENSE, component.GetID(), "not-compliant", score) + return newRecordStmt(COMP_LICENSE, component.GetName(), "not-compliant", score) } var spdx, aboutcode, custom int @@ -378,10 +378,10 @@ func bsiComponentLicense(component sbom.GetComponent) *record { if total != len(licenses) { score = 0.0 - return newRecordStmt(COMP_LICENSE, component.GetID(), "not-compliant", score) + return newRecordStmt(COMP_LICENSE, component.GetName(), "not-compliant", score) } - return newRecordStmt(COMP_LICENSE, component.GetID(), "compliant", 10.0) + return newRecordStmt(COMP_LICENSE, component.GetName(), "compliant", 10.0) } func bsiComponentSourceHash(component sbom.GetComponent) *record { @@ -393,7 +393,7 @@ func bsiComponentSourceHash(component sbom.GetComponent) *record { score = 10.0 } - return newRecordStmtOptional(COMP_SOURCE_HASH, component.GetID(), result, score) + return newRecordStmtOptional(COMP_SOURCE_HASH, component.GetName(), result, score) } func bsiComponentOtherUniqIDs(component sbom.GetComponent) *record { @@ -406,7 +406,7 @@ func bsiComponentOtherUniqIDs(component sbom.GetComponent) *record { result = string(purl[0]) score = 10.0 - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) } cpes := component.GetCpes() @@ -415,29 +415,29 @@ func bsiComponentOtherUniqIDs(component sbom.GetComponent) *record { result = string(cpes[0]) score = 10.0 - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), result, score) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) } - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetID(), "", 0.0) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), "", 0.0) } func bsiComponentDownloadURL(component sbom.GetComponent) *record { result := component.GetDownloadLocationURL() if result != "" { - return newRecordStmtOptional(COMP_DOWNLOAD_URL, component.GetID(), result, 10.0) + return newRecordStmtOptional(COMP_DOWNLOAD_URL, component.GetName(), result, 10.0) } - return newRecordStmtOptional(COMP_DOWNLOAD_URL, component.GetID(), "", 0.0) + return newRecordStmtOptional(COMP_DOWNLOAD_URL, component.GetName(), "", 0.0) } func bsiComponentSourceCodeURL(component sbom.GetComponent) *record { result := component.SourceCodeURL() if result != "" { - return newRecordStmtOptional(COMP_SOURCE_CODE_URL, component.GetID(), result, 10.0) + return newRecordStmtOptional(COMP_SOURCE_CODE_URL, component.GetName(), result, 10.0) } - return newRecordStmtOptional(COMP_SOURCE_CODE_URL, component.GetID(), "", 0.0) + return newRecordStmtOptional(COMP_SOURCE_CODE_URL, component.GetName(), "", 0.0) } func bsiComponentHash(component sbom.GetComponent) *record { @@ -455,27 +455,27 @@ func bsiComponentHash(component sbom.GetComponent) *record { } } - return newRecordStmt(COMP_HASH, component.GetID(), result, score) + return newRecordStmt(COMP_HASH, component.GetName(), result, score) } func bsiComponentVersion(component sbom.GetComponent) *record { result := component.GetVersion() if result != "" { - return newRecordStmt(COMP_VERSION, component.GetID(), result, 10.0) + return newRecordStmt(COMP_VERSION, component.GetName(), result, 10.0) } - return newRecordStmt(COMP_VERSION, component.GetID(), "", 0.0) + return newRecordStmt(COMP_VERSION, component.GetName(), "", 0.0) } func bsiComponentName(component sbom.GetComponent) *record { result := component.GetName() if result != "" { - return newRecordStmt(COMP_NAME, component.GetID(), result, 10.0) + return newRecordStmt(COMP_NAME, component.GetName(), result, 10.0) } - return newRecordStmt(COMP_NAME, component.GetID(), "", 0.0) + return newRecordStmt(COMP_NAME, component.GetName(), "", 0.0) } func bsiComponentCreator(component sbom.GetComponent) *record { @@ -490,7 +490,7 @@ func bsiComponentCreator(component sbom.GetComponent) *record { } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if supplier.GetURL() != "" { @@ -499,7 +499,7 @@ func bsiComponentCreator(component sbom.GetComponent) *record { } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if supplier.GetContacts() != nil { @@ -512,7 +512,7 @@ func bsiComponentCreator(component sbom.GetComponent) *record { } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } } } @@ -526,7 +526,7 @@ func bsiComponentCreator(component sbom.GetComponent) *record { } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if manufacturer.GetURL() != "" { @@ -535,7 +535,7 @@ func bsiComponentCreator(component sbom.GetComponent) *record { } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } if manufacturer.GetContacts() != nil { @@ -548,10 +548,10 @@ func bsiComponentCreator(component sbom.GetComponent) *record { } if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetID(), result, score) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } } } - return newRecordStmt(COMP_CREATOR, component.GetID(), "", 0.0) + return newRecordStmt(COMP_CREATOR, component.GetName(), "", 0.0) } diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index 9c87ec5..fc8861a 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -374,9 +374,7 @@ func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) * } } if results != nil { - for _, name := range results { - result += name + ", " - } + result = strings.Join(results, ", ") } else { result += "No Dependencies" } diff --git a/pkg/compliance/oct.go b/pkg/compliance/oct.go index 331cee9..a9da218 100644 --- a/pkg/compliance/oct.go +++ b/pkg/compliance/oct.go @@ -269,45 +269,45 @@ func octComponents(doc sbom.Document) []*record { func octPackageName(component sbom.GetComponent) *record { if result := component.GetName(); result != "" { - return newRecordStmt(PACK_NAME, component.GetID(), result, 10.0) + return newRecordStmt(PACK_NAME, component.GetName(), result, 10.0) } - return newRecordStmt(PACK_NAME, component.GetID(), "", 0.0) + return newRecordStmt(PACK_NAME, component.GetName(), "", 0.0) } func octPackageSpdxID(component sbom.GetComponent) *record { if result := component.GetSpdxID(); result != "" { - return newRecordStmt(PACK_SPDXID, component.GetID(), result, 10.0) + return newRecordStmt(PACK_SPDXID, component.GetName(), result, 10.0) } - return newRecordStmt(PACK_SPDXID, component.GetID(), "", 0.0) + return newRecordStmt(PACK_SPDXID, component.GetName(), "", 0.0) } func octPackageVersion(component sbom.GetComponent) *record { if result := component.GetVersion(); result != "" { - return newRecordStmt(PACK_VERSION, component.GetID(), result, 10.0) + return newRecordStmt(PACK_VERSION, component.GetName(), result, 10.0) } - return newRecordStmt(PACK_VERSION, component.GetID(), "", 0.0) + return newRecordStmt(PACK_VERSION, component.GetName(), "", 0.0) } func octPackageSupplier(component sbom.GetComponent) *record { if supplier := component.Suppliers().GetEmail(); supplier != "" { - return newRecordStmt(PACK_SUPPLIER, component.GetID(), supplier, 10.0) + return newRecordStmt(PACK_SUPPLIER, component.GetName(), supplier, 10.0) } - return newRecordStmt(PACK_SUPPLIER, component.GetID(), "", 0.0) + return newRecordStmt(PACK_SUPPLIER, component.GetName(), "", 0.0) } func octPackageDownloadURL(component sbom.GetComponent) *record { if result := component.GetDownloadLocationURL(); result != "" { - return newRecordStmt(PACK_DOWNLOAD_URL, component.GetID(), result, 10.0) + return newRecordStmt(PACK_DOWNLOAD_URL, component.GetName(), result, 10.0) } - return newRecordStmt(PACK_DOWNLOAD_URL, component.GetID(), "", 0.0) + return newRecordStmt(PACK_DOWNLOAD_URL, component.GetName(), "", 0.0) } func octPackageFileAnalyzed(component sbom.GetComponent) *record { if result := component.GetFileAnalyzed(); result { - return newRecordStmt(PACK_FILE_ANALYZED, component.GetID(), "yes", 10.0) + return newRecordStmt(PACK_FILE_ANALYZED, component.GetName(), "yes", 10.0) } - return newRecordStmt(PACK_FILE_ANALYZED, component.GetID(), "no", 0.0) + return newRecordStmt(PACK_FILE_ANALYZED, component.GetName(), "no", 0.0) } func octPackageHash(component sbom.GetComponent) *record { @@ -324,37 +324,37 @@ func octPackageHash(component sbom.GetComponent) *record { } } - return newRecordStmt(PACK_HASH, component.GetID(), result, score) + return newRecordStmt(PACK_HASH, component.GetName(), result, score) } func octPackageConLicense(component sbom.GetComponent) *record { result := "" if result = component.GetPackageLicenseConcluded(); result != "" && result != "NOASSERTION" && result != "NONE" { - return newRecordStmt(PACK_LICENSE_CON, component.GetID(), result, 10.0) + return newRecordStmt(PACK_LICENSE_CON, component.GetName(), result, 10.0) } - return newRecordStmt(PACK_LICENSE_CON, component.GetID(), result, 0.0) + return newRecordStmt(PACK_LICENSE_CON, component.GetName(), result, 0.0) } func octPackageDecLicense(component sbom.GetComponent) *record { result := "" if result = component.GetPackageLicenseDeclared(); result != "" && result != "NOASSERTION" && result != "NONE" { - return newRecordStmt(PACK_LICENSE_DEC, component.GetID(), result, 10.0) + return newRecordStmt(PACK_LICENSE_DEC, component.GetName(), result, 10.0) } - return newRecordStmt(PACK_LICENSE_DEC, component.GetID(), result, 0.0) + return newRecordStmt(PACK_LICENSE_DEC, component.GetName(), result, 0.0) } func octPackageCopyright(component sbom.GetComponent) *record { result := "" if result = component.GetCopyRight(); result != "" && result != "NOASSERTION" && result != "NONE" { - return newRecordStmt(PACK_COPYRIGHT, component.GetID(), result, 10.0) + return newRecordStmt(PACK_COPYRIGHT, component.GetName(), result, 10.0) } - return newRecordStmt(PACK_COPYRIGHT, component.GetID(), result, 0.0) + return newRecordStmt(PACK_COPYRIGHT, component.GetName(), result, 0.0) } func octPackageExternalRefs(component sbom.GetComponent) *record { @@ -374,5 +374,5 @@ func octPackageExternalRefs(component sbom.GetComponent) *record { x := fmt.Sprintf(":(%d/%d)", containPurlElement, totalElements) result = result + x } - return newRecordStmt(PACK_EXT_REF, component.GetID(), result, score) + return newRecordStmt(PACK_EXT_REF, component.GetName(), result, score) } From 9e54452eeb777fce15255b85f03b6f882f559f00 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Tue, 10 Sep 2024 14:48:14 +0530 Subject: [PATCH 17/19] fix dependencies and compositions Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/bsi.go | 70 ++++++++++---------- pkg/compliance/ntia.go | 19 +++--- pkg/sbom/cdx.go | 111 +++++++++++++++++-------------- pkg/sbom/component.go | 6 ++ pkg/sbom/document.go | 1 + pkg/sbom/primarycomp.go | 11 +++- pkg/sbom/spdx.go | 141 +++++++++++++++++++++++++++++++++------- 7 files changed, 243 insertions(+), 116 deletions(-) diff --git a/pkg/compliance/bsi.go b/pkg/compliance/bsi.go index fded9a0..5fed3dc 100644 --- a/pkg/compliance/bsi.go +++ b/pkg/compliance/bsi.go @@ -16,6 +16,7 @@ package compliance import ( "context" + "fmt" "strings" "github.com/interlynk-io/sbomqs/pkg/logger" @@ -161,31 +162,16 @@ func bsiBuildPhase(doc sbom.Document) *record { } func bsiSbomDepth(doc sbom.Document) *record { - if !doc.PrimaryComp().Present() { - return newRecordStmt(SBOM_DEPTH, "doc", "no-primary", 0.0) - } - - if len(doc.Relations()) == 0 { - return newRecordStmt(SBOM_DEPTH, "doc", "no-relationships", 0.0) - } - - primary, _ := lo.Find(doc.Components(), func(c sbom.GetComponent) bool { - return c.IsPrimaryComponent() - }) - - if !primary.HasRelationShips() { - return newRecordStmt(SBOM_DEPTH, "doc", "no-primary-relationships", 0.0) - } - - if primary.RelationShipState() == "complete" { - return newRecordStmt(SBOM_DEPTH, "doc", "complete", 10.0) - } + result, score := "", 0.0 + // for doc.Components() + totalDependencies := doc.PrimaryComp().Dependencies() - if primary.HasRelationShips() { - return newRecordStmt(SBOM_DEPTH, "doc", "unattested-has-relationships", 5.0) + if totalDependencies > 0 { + score = 10.0 } + result = fmt.Sprintf("doc has %d depedencies", totalDependencies) - return newRecordStmt(SBOM_DEPTH, "doc", "non-compliant", 0.0) + return newRecordStmt(SBOM_DEPTH, "doc", result, score) } func bsiCreator(doc sbom.Document) *record { @@ -306,13 +292,17 @@ func bsiComponents(doc sbom.Document) []*record { records := append(records, newRecordStmt(SBOM_COMPONENTS, "doc", "", 0.0)) return records } + // map package ID to Package Name + for _, component := range doc.Components() { + CompIDWithName[component.GetID()] = component.GetName() + } for _, component := range doc.Components() { records = append(records, bsiComponentCreator(component)) records = append(records, bsiComponentName(component)) records = append(records, bsiComponentVersion(component)) records = append(records, bsiComponentLicense(component)) - records = append(records, bsiComponentDepth(component)) + records = append(records, bsiComponentDepth(doc, component)) records = append(records, bsiComponentHash(component)) records = append(records, bsiComponentSourceCodeURL(component)) records = append(records, bsiComponentDownloadURL(component)) @@ -325,20 +315,36 @@ func bsiComponents(doc sbom.Document) []*record { return records } -func bsiComponentDepth(component sbom.GetComponent) *record { - if !component.HasRelationShips() { +func bsiComponentDepth(doc sbom.Document, component sbom.GetComponent) *record { + result, score := "", 0.0 + var fResults []string + + dependencies := doc.GetRelationships(component.GetID()) + if dependencies == nil { return newRecordStmt(COMP_DEPTH, component.GetName(), "no-relationships", 0.0) } - if component.RelationShipState() == "complete" { - return newRecordStmt(COMP_DEPTH, component.GetName(), "complete", 10.0) + for _, d := range dependencies { + state := component.GetComposition(d) + if state == "complete" { + componentName := extractName(d) + fResults = append(fResults, componentName) + score = 10.0 + } else { + componentName := extractName(d) + // state := "(unattested-has-relationships)" + fResults = append(fResults, componentName) + score = 5.0 + } } - if component.HasRelationShips() { - return newRecordStmt(COMP_DEPTH, component.GetName(), "unattested-has-relationships", 5.0) + if fResults != nil { + result = strings.Join(fResults, ", ") + } else { + result += "no-relationships" } - return newRecordStmt(COMP_DEPTH, component.GetName(), "non-compliant", 0.0) + return newRecordStmt(COMP_DEPTH, component.GetName(), result, score) } func bsiComponentLicense(component sbom.GetComponent) *record { @@ -346,7 +352,6 @@ func bsiComponentLicense(component sbom.GetComponent) *record { score := 0.0 if len(licenses) == 0 { - // fmt.Printf("component %s : %s has no licenses\n", component.Name(), component.Version()) return newRecordStmt(COMP_LICENSE, component.GetName(), "not-compliant", score) } @@ -373,9 +378,6 @@ func bsiComponentLicense(component sbom.GetComponent) *record { total := spdx + aboutcode + custom - // fmt.Printf("component %s : %s has (total)%d = (all)%d licenses\n", component.Name(), component.Version(), total, len(licenses)) - // fmt.Printf("%+v\n", licenses) - if total != len(licenses) { score = 0.0 return newRecordStmt(COMP_LICENSE, component.GetName(), "not-compliant", score) diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index fc8861a..72e62fb 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -364,19 +364,20 @@ func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) * result, score := "", 0.0 var results []string - if relation := doc.Relations(); relation != nil { - for _, rel := range relation { - if strings.Contains(rel.GetFrom(), component.GetID()) { - componentName := extractName(rel.GetTo()) - results = append(results, componentName) - score = 10.0 - } - } + dependencies := doc.GetRelationships(component.GetID()) + if dependencies == nil { + return newRecordStmt(COMP_DEPTH, component.GetName(), "no-relationships", 0.0) } + for _, d := range dependencies { + componentName := extractName(d) + results = append(results, componentName) + score = 10.0 + } + if results != nil { result = strings.Join(results, ", ") } else { - result += "No Dependencies" + result += "no-relationships" } return newRecordStmt(COMP_DEPTH, component.GetName(), result, score) diff --git a/pkg/sbom/cdx.go b/pkg/sbom/cdx.go index 103f4ae..23aa5d2 100644 --- a/pkg/sbom/cdx.go +++ b/pkg/sbom/cdx.go @@ -50,6 +50,8 @@ type CdxDoc struct { manufacturer Manufacturer compositions map[string]string primaryComp primaryComp + dependencies map[string][]string + composition map[string]string } func newCDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Document, error) { @@ -129,6 +131,14 @@ func (c CdxDoc) Manufacturer() Manufacturer { return c.manufacturer } +func (c CdxDoc) GetRelationships(componentID string) []string { + return c.dependencies[componentID] +} + +func (c CdxDoc) GetComposition(componentID string) string { + return c.composition[componentID] +} + func (c *CdxDoc) parse() { c.parseDoc() c.parseSpec() @@ -137,7 +147,7 @@ func (c *CdxDoc) parse() { c.parseManufacturer() c.parseTool() c.parseCompositions() - c.parseRelsAndPrimaryComp() + c.parsePrimaryCompAndRelationships() c.parseComps() } @@ -287,49 +297,6 @@ func copyC(cdxc *cydx.Component, c *CdxDoc) *Component { nc.isPrimary = true } - fromRelsPresent := func(rels []GetRelation, compID string) bool { - for _, r := range rels { - if r.GetFrom() == compID { - return true - } - } - return false - } - - compNormalise := func(compID string) string { - switch cydx.CompositionAggregate(compID) { - case cydx.CompositionAggregateComplete: - return "complete" - case cydx.CompositionAggregateIncomplete: - return "incomplete" - case cydx.CompositionAggregateIncompleteFirstPartyOnly: - return "incomplete-first-party-only" - case cydx.CompositionAggregateIncompleteFirstPartyOpenSourceOnly: - return "incomplete-first-party-open-source-only" - case cydx.CompositionAggregateIncompleteFirstPartyProprietaryOnly: - return "incomplete-first-party-proprietary-only" - case cydx.CompositionAggregateIncompleteThirdPartyOnly: - return "incomplete-third-party-only" - case cydx.CompositionAggregateIncompleteThirdPartyOpenSourceOnly: - return "incomplete-third-party-open-source-only" - case cydx.CompositionAggregateIncompleteThirdPartyProprietaryOnly: - return "incomplete-third-party-proprietary-only" - case cydx.CompositionAggregateNotSpecified: - return "not-specified" - case cydx.CompositionAggregateUnknown: - return "unknown" - } - - return "not-specified" - } - - nc.hasRelationships = fromRelsPresent(c.rels, cdxc.BOMRef) - if c.compositions != nil { - if comp, ok := c.compositions[cdxc.BOMRef]; ok { - nc.RelationshipState = compNormalise(comp) - } - } - nc.ID = cdxc.BOMRef return nc } @@ -545,14 +512,16 @@ func (c *CdxDoc) parseManufacturer() { c.manufacturer = m } -func (c *CdxDoc) parseRelsAndPrimaryComp() { +func (c *CdxDoc) parsePrimaryCompAndRelationships() { if c.doc.Metadata == nil { return } - if c.doc.Metadata.Component == nil { return } + + c.dependencies = make(map[string][]string) + c.primaryComp.present = true c.primaryComp.id = c.doc.Metadata.Component.BOMRef var totalDependencies int @@ -563,16 +532,62 @@ func (c *CdxDoc) parseRelsAndPrimaryComp() { for _, d := range lo.FromPtr(r.Dependencies) { nr := Relation{} nr.From = r.Ref + nr.To = d if r.Ref == c.primaryComp.id { + c.primaryComp.hasDependencies = true totalDependencies++ + c.rels = append(c.rels, nr) + c.dependencies[c.primaryComp.id] = append(c.dependencies[c.primaryComp.id], d) + } else { + c.rels = append(c.rels, nr) + c.dependencies[r.Ref] = append(c.dependencies[r.Ref], d) } - nr.To = d - c.rels = append(c.rels, nr) } } c.primaryComp.dependecies = totalDependencies } +func (c *CdxDoc) parseComposition() { + if c.doc.Metadata == nil { + return + } + if c.doc.Compositions == nil { + return + } + c.composition = make(map[string]string) + + for _, cp := range lo.FromPtr(c.doc.Compositions) { + state := compNormalise(cp.BOMRef) + c.composition[cp.BOMRef] = state + } +} + +func compNormalise(compID string) string { + switch cydx.CompositionAggregate(compID) { + case cydx.CompositionAggregateComplete: + return "complete" + case cydx.CompositionAggregateIncomplete: + return "incomplete" + case cydx.CompositionAggregateIncompleteFirstPartyOnly: + return "incomplete-first-party-only" + case cydx.CompositionAggregateIncompleteFirstPartyOpenSourceOnly: + return "incomplete-first-party-open-source-only" + case cydx.CompositionAggregateIncompleteFirstPartyProprietaryOnly: + return "incomplete-first-party-proprietary-only" + case cydx.CompositionAggregateIncompleteThirdPartyOnly: + return "incomplete-third-party-only" + case cydx.CompositionAggregateIncompleteThirdPartyOpenSourceOnly: + return "incomplete-third-party-open-source-only" + case cydx.CompositionAggregateIncompleteThirdPartyProprietaryOnly: + return "incomplete-third-party-proprietary-only" + case cydx.CompositionAggregateNotSpecified: + return "not-specified" + case cydx.CompositionAggregateUnknown: + return "unknown" + } + return "not-specified" +} + func (c *CdxDoc) assignSupplier(comp *cydx.Component) *Supplier { if comp.Supplier == nil { c.addToLogs(fmt.Sprintf("cdx doc comp %s no supplier found", comp.Name)) diff --git a/pkg/sbom/component.go b/pkg/sbom/component.go index 70d01e7..02fae3b 100644 --- a/pkg/sbom/component.go +++ b/pkg/sbom/component.go @@ -46,6 +46,7 @@ type GetComponent interface { GetPackageLicenseDeclared() string GetPackageLicenseConcluded() string ExternalReferences() []GetExternalReference + GetComposition(string) string } type Component struct { @@ -73,6 +74,7 @@ type Component struct { PackageLicenseConcluded string PackageLicenseDeclared string ExternalRefs []GetExternalReference + composition map[string]string } func NewComponent() *Component { @@ -174,3 +176,7 @@ func (c Component) GetPackageLicenseDeclared() string { func (c Component) ExternalReferences() []GetExternalReference { return c.ExternalRefs } + +func (c Component) GetComposition(componentID string) string { + return c.composition[componentID] +} diff --git a/pkg/sbom/document.go b/pkg/sbom/document.go index a47b062..2a4d122 100644 --- a/pkg/sbom/document.go +++ b/pkg/sbom/document.go @@ -30,4 +30,5 @@ type Document interface { Supplier() GetSupplier PrimaryComp() PrimaryComp + GetRelationships(string) []string } diff --git a/pkg/sbom/primarycomp.go b/pkg/sbom/primarycomp.go index e1df1b8..b856dc7 100644 --- a/pkg/sbom/primarycomp.go +++ b/pkg/sbom/primarycomp.go @@ -21,9 +21,10 @@ type PrimaryComp interface { } type primaryComp struct { - present bool - id string - dependecies int + present bool + id string + dependecies int + hasDependencies bool } func (pc *primaryComp) Present() bool { @@ -37,3 +38,7 @@ func (pc *primaryComp) ID() string { func (pc *primaryComp) Dependencies() int { return pc.dependecies } + +func (pc *primaryComp) HasDependencies() bool { + return pc.hasDependencies +} diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index 61aceb4..c7e0c6b 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -43,17 +43,19 @@ var ( ) type SpdxDoc struct { - doc *spdx.Document - format FileFormat - ctx context.Context - SpdxSpec *Specs - Comps []GetComponent - authors []GetAuthor - SpdxTools []GetTool - Rels []GetRelation - logs []string - primaryComp primaryComp - lifecycles string + doc *spdx.Document + format FileFormat + ctx context.Context + SpdxSpec *Specs + Comps []GetComponent + authors []GetAuthor + SpdxTools []GetTool + Rels []GetRelation + logs []string + primaryComp primaryComp + lifecycles string + dependencies map[string][]string + composition map[string]string } func newSPDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Document, error) { @@ -135,11 +137,19 @@ func (s SpdxDoc) Supplier() GetSupplier { return nil } +func (s SpdxDoc) GetRelationships(componentID string) []string { + return s.dependencies[componentID] +} + +func (s SpdxDoc) GetComposition(componentID string) string { + return s.composition[componentID] +} + func (s *SpdxDoc) parse() { s.parseSpec() s.parseAuthors() s.parseTool() - s.parseRelsAndPrimaryComp() + s.parsePrimaryCompAndRelationships() s.parseComps() } @@ -276,14 +286,16 @@ func (s *SpdxDoc) parseAuthors() { } } -func (s *SpdxDoc) parseRelsAndPrimaryComp() { +func (s *SpdxDoc) parsePrimaryCompAndRelationships() { s.Rels = []GetRelation{} + s.dependencies = make(map[string][]string) var err error var aBytes, bBytes []byte var primaryComponent string var totalDependencies int for _, r := range s.doc.Relationships { + // check relation type DESCRIBE if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDescribe { bBytes, err = r.RefB.ElementRefID.MarshalJSON() if err != nil { @@ -294,21 +306,18 @@ func (s *SpdxDoc) parseRelsAndPrimaryComp() { s.primaryComp.present = true } } - // If no primary component found, return early - if primaryComponent == "" { - return - } + // if s.primaryComp.present { for _, r := range s.doc.Relationships { if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDependsOn { aBytes, err = r.RefA.MarshalJSON() if err != nil { continue } - bBytes, err = r.RefB.ElementRefID.MarshalJSON() - if err != nil { - continue - } + // bBytes, err = r.RefB.ElementRefID.MarshalJSON() + // if err != nil { + // continue + // } if string(aBytes) == primaryComponent { bBytes, err = r.RefB.MarshalJSON() @@ -323,18 +332,106 @@ func (s *SpdxDoc) parseRelsAndPrimaryComp() { totalDependencies++ s.Rels = append(s.Rels, nr) + s.dependencies[primaryComponent] = append(s.dependencies[primaryComponent], string(bBytes)) } else { nr := Relation{ From: string(aBytes), To: string(bBytes), } + s.dependencies[string(aBytes)] = append(s.dependencies[string(aBytes)], string(bBytes)) s.Rels = append(s.Rels, nr) } } } - s.primaryComp.dependecies = totalDependencies + // } } +// func (s *SpdxDoc) parseComponentDepedencies() { +// s.Rels = []GetRelation{} +// s.dependencies = make(map[string][]string) +// var err error +// var aBytes, bBytes []byte + +// for _, r := range s.doc.Relationships { +// if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDependsOn { +// aBytes, err = r.RefA.MarshalJSON() +// if err != nil { +// continue +// } +// bBytes, err = r.RefB.ElementRefID.MarshalJSON() +// if err != nil { +// continue +// } + +// nr := Relation{ +// From: string(aBytes), +// To: string(bBytes), +// } +// s.Rels = append(s.Rels, nr) +// s.dependencies[string(aBytes)] = append(s.dependencies[string(aBytes)], string(bBytes)) +// } +// } +// } + +// func (s *SpdxDoc) parseRelsAndPrimaryComp() { +// s.Rels = []GetRelation{} +// var err error +// var aBytes, bBytes []byte +// var primaryComponent string +// var totalDependencies int + +// for _, r := range s.doc.Relationships { +// if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDescribe { +// bBytes, err = r.RefB.ElementRefID.MarshalJSON() +// if err != nil { +// continue +// } +// primaryComponent = string(bBytes) +// s.primaryComp.id = primaryComponent +// s.primaryComp.present = true +// } +// } +// // If no primary component found, return early +// if primaryComponent == "" { +// return +// } + +// for _, r := range s.doc.Relationships { +// if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDependsOn { +// aBytes, err = r.RefA.MarshalJSON() +// if err != nil { +// continue +// } +// bBytes, err = r.RefB.ElementRefID.MarshalJSON() +// if err != nil { +// continue +// } + +// if string(aBytes) == primaryComponent { +// bBytes, err = r.RefB.MarshalJSON() +// if err != nil { +// continue +// } + +// nr := Relation{ +// From: primaryComponent, +// To: string(bBytes), +// } +// totalDependencies++ + +// s.Rels = append(s.Rels, nr) +// } else { +// nr := Relation{ +// From: string(aBytes), +// To: string(bBytes), +// } +// s.Rels = append(s.Rels, nr) +// } +// } +// } +// s.primaryComp.dependecies = totalDependencies +// } + // creationInfo.Creators.Tool // Also create for org: , creationInfo.Creators.Organization func (s *SpdxDoc) parseTool() { From 8c1b3dbd6b5cd9992a483021c9ca4fe7454ddd23 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Wed, 11 Sep 2024 10:34:27 +0530 Subject: [PATCH 18/19] add test ci workflow Signed-off-by: Vivek Kumar Sahu --- .github/workflows/ci.yml | 25 ++++++++++++++ pkg/compliance/ntia_test.go | 65 +++++++++++++++++++++++++------------ 2 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3ee32c1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '>=1.20' + + - name: Install dependencies + run: go mod download + + - name: Run tests + run: go test ./... -v diff --git a/pkg/compliance/ntia_test.go b/pkg/compliance/ntia_test.go index 208e55c..b037ccf 100644 --- a/pkg/compliance/ntia_test.go +++ b/pkg/compliance/ntia_test.go @@ -42,8 +42,8 @@ func createSpdxDummyDocumentNtia() sbom.Document { packages = append(packages, pack) depend := sbom.Relation{ - From: "SPDXRef-Package-go-module-github.com-abc", - To: "SPDXRef-Package-go-module-github.com-xyz", + From: "github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1", + To: "github/spdx/gordf@b735bd5aac89fe25cad4ef488a95bc00ea549edd", } var dependencies []sbom.GetRelation dependencies = append(dependencies, depend) @@ -67,10 +67,12 @@ type desiredNtia struct { func TestNtiaSpdxSbomPass(t *testing.T) { doc := createSpdxDummyDocumentNtia() testCases := []struct { + name string actual *record expected desiredNtia }{ { + name: "AutomationSpec", actual: ntiaAutomationSpec(doc), expected: desiredNtia{ score: 10.0, @@ -80,6 +82,7 @@ func TestNtiaSpdxSbomPass(t *testing.T) { }, }, { + name: "SbomCreator", actual: ntiaSbomCreator(doc), expected: desiredNtia{ score: 10.0, @@ -89,6 +92,7 @@ func TestNtiaSpdxSbomPass(t *testing.T) { }, }, { + name: "SbomCreatedTimestamp", actual: ntiaSbomCreatedTimestamp(doc), expected: desiredNtia{ score: 10.0, @@ -98,58 +102,63 @@ func TestNtiaSpdxSbomPass(t *testing.T) { }, }, { + name: "ComponentCreator", actual: ntiaComponentCreator(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, result: "hello@interlynk.io", key: PACK_SUPPLIER, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { + name: "ComponentName", actual: ntiaComponentName(doc.Components()[0]), expected: desiredNtia{ score: 10.0, result: "core-js", key: COMP_NAME, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { + name: "ComponentVersion", actual: ntiaComponentVersion(doc.Components()[0]), expected: desiredNtia{ score: 10.0, result: "v0.7.1", key: COMP_VERSION, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { + name: "ComponentOtherUniqIDs", actual: ntiaComponentOtherUniqIDs(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, result: "purl:(1/1)", key: COMP_OTHER_UNIQ_IDS, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { + name: "ComponentDependencies", actual: ntiaComponentDependencies(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, - result: "SPDXRef-Package-go-module-github.com-xyz", + result: "gordf", key: COMP_DEPTH, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, } for _, test := range testCases { - assert.Equal(t, test.expected.score, test.actual.score) - assert.Equal(t, test.expected.key, test.actual.checkKey) - assert.Equal(t, test.expected.id, test.actual.id) - assert.Equal(t, test.expected.result, test.actual.checkValue) + assert.Equal(t, test.expected.score, test.actual.score, "Score mismatch for %s", test.name) + assert.Equal(t, test.expected.key, test.actual.checkKey, "Key mismatch for %s", test.name) + assert.Equal(t, test.expected.id, test.actual.id, "ID mismatch for %s", test.name) + assert.Equal(t, test.expected.result, test.actual.checkValue, "Result mismatch for %s", test.name) } } @@ -201,10 +210,12 @@ func createCdxDummyDocumentNtia() sbom.Document { func TestNtiaCdxSbomPass(t *testing.T) { doc := createCdxDummyDocumentNtia() testCases := []struct { + name string actual *record expected desiredNtia }{ { + name: "AutomationSpec", actual: ntiaAutomationSpec(doc), expected: desiredNtia{ score: 10.0, @@ -214,6 +225,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, }, { + name: "SbomCreator", actual: ntiaSbomCreator(doc), expected: desiredNtia{ score: 10.0, @@ -223,6 +235,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, }, { + name: "SbomCreatedTimestamp", actual: ntiaSbomCreatedTimestamp(doc), expected: desiredNtia{ score: 10.0, @@ -232,6 +245,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, }, { + name: "ComponentCreator", actual: ntiaComponentCreator(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, @@ -241,6 +255,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, }, { + name: "ComponentName", actual: ntiaComponentName(doc.Components()[0]), expected: desiredNtia{ score: 10.0, @@ -250,6 +265,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, }, { + name: "ComponentVersion", actual: ntiaComponentVersion(doc.Components()[0]), expected: desiredNtia{ score: 10.0, @@ -259,6 +275,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, }, { + name: "ComponentOtherUniqIDs", actual: ntiaComponentOtherUniqIDs(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, @@ -269,10 +286,10 @@ func TestNtiaCdxSbomPass(t *testing.T) { }, } for _, test := range testCases { - assert.Equal(t, test.expected.score, test.actual.score) - assert.Equal(t, test.expected.key, test.actual.checkKey) - assert.Equal(t, test.expected.id, test.actual.id) - assert.Equal(t, test.expected.result, test.actual.checkValue) + assert.Equal(t, test.expected.score, test.actual.score, "Score mismatch for %s", test.name) + assert.Equal(t, test.expected.key, test.actual.checkKey, "Key mismatch for %s", test.name) + assert.Equal(t, test.expected.id, test.actual.id, "ID mismatch for %s", test.name) + assert.Equal(t, test.expected.result, test.actual.checkValue, "Result mismatch for %s", test.name) } } @@ -328,10 +345,12 @@ func createSpdxDummyDocumentFailNtia() sbom.Document { func TestNTIASbomFail(t *testing.T) { doc := createSpdxDummyDocumentFailNtia() testCases := []struct { + name string actual *record expected desiredNtia }{ { + name: "AutomationSpec", actual: ntiaAutomationSpec(doc), expected: desiredNtia{ score: 0.0, @@ -341,6 +360,7 @@ func TestNTIASbomFail(t *testing.T) { }, }, { + name: "SbomCreator", actual: ntiaSbomCreator(doc), expected: desiredNtia{ score: 0.0, @@ -350,6 +370,7 @@ func TestNTIASbomFail(t *testing.T) { }, }, { + name: "SbomCreatedTimestamp", actual: ntiaSbomCreatedTimestamp(doc), expected: desiredNtia{ score: 0.0, @@ -359,6 +380,7 @@ func TestNTIASbomFail(t *testing.T) { }, }, { + name: "ComponentCreator", actual: ntiaComponentCreator(doc, doc.Components()[0]), expected: desiredNtia{ score: 0.0, @@ -369,6 +391,7 @@ func TestNTIASbomFail(t *testing.T) { }, { + name: "ComponentName", actual: ntiaComponentName(doc.Components()[0]), expected: desiredNtia{ score: 0.0, @@ -378,6 +401,7 @@ func TestNTIASbomFail(t *testing.T) { }, }, { + name: "ComponentVersion", actual: ntiaComponentVersion(doc.Components()[0]), expected: desiredNtia{ score: 0.0, @@ -387,6 +411,7 @@ func TestNTIASbomFail(t *testing.T) { }, }, { + name: "ComponentOtherUniqIDs", actual: ntiaComponentOtherUniqIDs(doc, doc.Components()[0]), expected: desiredNtia{ score: 0.0, @@ -398,9 +423,9 @@ func TestNTIASbomFail(t *testing.T) { } for _, test := range testCases { - assert.Equal(t, test.expected.score, test.actual.score) - assert.Equal(t, test.expected.key, test.actual.checkKey) - assert.Equal(t, test.expected.id, test.actual.id) - assert.Equal(t, test.expected.result, test.actual.checkValue) + assert.Equal(t, test.expected.score, test.actual.score, "Score mismatch for %s", test.name) + assert.Equal(t, test.expected.key, test.actual.checkKey, "Key mismatch for %s", test.name) + assert.Equal(t, test.expected.id, test.actual.id, "ID mismatch for %s", test.name) + assert.Equal(t, test.expected.result, test.actual.checkValue, "Result mismatch for %s", test.name) } } From 567737e4978dd40c2c1eadf336a4813d39ecebe4 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Sahu Date: Thu, 12 Sep 2024 15:42:41 +0530 Subject: [PATCH 19/19] updated test acc to dependencies updates Signed-off-by: Vivek Kumar Sahu --- pkg/compliance/bsi.go | 4 +- pkg/compliance/ntia.go | 339 +++++++++++++++--------------------- pkg/compliance/ntia_test.go | 88 +++++++--- pkg/compliance/oct_test.go | 68 +++++--- pkg/engine/score_test.go | 2 +- pkg/sbom/cdx.go | 58 +++--- pkg/sbom/document.go | 2 +- pkg/sbom/primarycomp.go | 30 ++-- pkg/sbom/spdx.go | 139 +++------------ pkg/scorer/ntia.go | 2 +- pkg/scorer/quality.go | 2 +- 11 files changed, 326 insertions(+), 408 deletions(-) diff --git a/pkg/compliance/bsi.go b/pkg/compliance/bsi.go index 5fed3dc..602430d 100644 --- a/pkg/compliance/bsi.go +++ b/pkg/compliance/bsi.go @@ -164,12 +164,12 @@ func bsiBuildPhase(doc sbom.Document) *record { func bsiSbomDepth(doc sbom.Document) *record { result, score := "", 0.0 // for doc.Components() - totalDependencies := doc.PrimaryComp().Dependencies() + totalDependencies := doc.PrimaryComp().GetTotalNoOfDependencies() if totalDependencies > 0 { score = 10.0 } - result = fmt.Sprintf("doc has %d depedencies", totalDependencies) + result = fmt.Sprintf("doc has %d dependencies", totalDependencies) return newRecordStmt(SBOM_DEPTH, "doc", result, score) } diff --git a/pkg/compliance/ntia.go b/pkg/compliance/ntia.go index 72e62fb..2d01756 100644 --- a/pkg/compliance/ntia.go +++ b/pkg/compliance/ntia.go @@ -30,6 +30,12 @@ var ( validFormats = []string{"json", "xml", "yaml", "yml", "tag-value"} ) +// nolint +const ( + SCORE_FULL = 10.0 + SCORE_ZERO = 0.0 +) + func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) { log := logger.FromContext(ctx) log.Debug("compliance.ntiaResult()") @@ -57,167 +63,155 @@ func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outForm // format func ntiaAutomationSpec(doc sbom.Document) *record { + result, score := "", SCORE_ZERO spec := doc.Spec().GetSpecType() - result, score := "", 0.0 - fileFormat := doc.Spec().FileFormat() + + result = spec + ", " + fileFormat + if lo.Contains(validFormats, fileFormat) && lo.Contains(validSpec, spec) { result = spec + ", " + fileFormat - score = 10.0 - } else { - result = spec + ", " + fileFormat + score = SCORE_FULL } return newRecordStmt(SBOM_MACHINE_FORMAT, "Automation Support", result, score) } func ntiaSBOMDependency(doc sbom.Document) *record { - result, score := "", 0.0 - // for doc.Components() - totalDependencies := doc.PrimaryComp().Dependencies() + result, score := "", SCORE_ZERO + totalRootDependencies := doc.PrimaryComp().GetTotalNoOfDependencies() - if totalDependencies > 0 { - score = 10.0 + if totalRootDependencies > 0 { + score = SCORE_FULL } - result = fmt.Sprintf("doc has %d depedencies", totalDependencies) + result = fmt.Sprintf("doc has %d dependencies", totalRootDependencies) return newRecordStmt(SBOM_DEPENDENCY, "SBOM Data Fields", result, score) } -// Required Sbom stuffs func ntiaSbomCreator(doc sbom.Document) *record { spec := doc.Spec().GetSpecType() - result, score := "", 0.0 + result, score := "", SCORE_ZERO - if spec == "spdx" { - name, email := "", "" + switch spec { + case "spdx": if tools := doc.Tools(); tools != nil { - for _, tool := range tools { - if name = tool.GetName(); name != "" { - result = name - score = 10.0 - break - } + if toolResult, found := getToolInfo(tools); found { + result = toolResult + score = SCORE_FULL + break } } if authors := doc.Authors(); authors != nil { - for _, author := range authors { - if name = author.GetName(); name != "" { - result = name - score = 10.0 - break - } else if email = author.GetEmail(); email != "" { - result = name - score = 10.0 - break - } + if authorResult, found := getAuthorInfo(authors); found { + result = authorResult + score = SCORE_FULL + break } } - } else if spec == "cyclonedx" { - for _, author := range doc.Authors() { - if author.GetEmail() != "" { - result = author.GetEmail() - score = 10.0 + case "cyclonedx": + if authors := doc.Authors(); authors != nil { + if authorResult, found := getAuthorInfo(authors); found { + result = authorResult + score = SCORE_FULL break } } - if result != "" { return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) } - - tools := doc.Tools() - - for _, tool := range tools { - if name := tool.GetName(); name != "" { - result = name - score = 10.0 + if tools := doc.Tools(); tools != nil { + if toolResult, found := getToolInfo(tools); found { + result = toolResult + score = SCORE_FULL break } } - - supplier := doc.Supplier() - - if supplier != nil { - if supplier.GetEmail() != "" { - result = supplier.GetEmail() - score = 10.0 - } - - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) - } - - if supplier.GetURL() != "" { - result = supplier.GetURL() - score = 10.0 - } - - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) - } - - for _, contact := range supplier.GetContacts() { - if contact.Email() != "" { - result = contact.Email() - score = 10.0 - break - } - } - - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) + if supplier := doc.Supplier(); supplier != nil { + if supplierResult, found := getSupplierInfo(supplier); found { + result = supplierResult + score = SCORE_FULL + break } - } - - manufacturer := doc.Manufacturer() - - if manufacturer != nil { - if manufacturer.GetEmail() != "" { - result = manufacturer.GetEmail() - score = 10.0 - } - - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) + if manufacturer := doc.Manufacturer(); manufacturer != nil { + if manufacturerResult, found := getManufacturerInfo(manufacturer); found { + result = manufacturerResult + score = SCORE_FULL + break } + } + } - if manufacturer.GetURL() != "" { - result = manufacturer.GetURL() - score = 10.0 - } + return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) +} - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) - } +func getManufacturerInfo(manufacturer sbom.Manufacturer) (string, bool) { + if manufacturer == nil { + return "", false + } + if email := manufacturer.GetEmail(); email != "" { + return email, true + } + if url := manufacturer.GetURL(); url != "" { + return url, true + } + for _, contact := range manufacturer.GetContacts() { + if email := contact.Email(); email != "" { + return email, true + } + } + return "", false +} - for _, contact := range manufacturer.GetContacts() { - if contact.Email() != "" { - result = contact.Email() - score = 10.0 - break - } - } +func getSupplierInfo(supplier sbom.GetSupplier) (string, bool) { + if supplier == nil { + return "", false + } + if email := supplier.GetEmail(); email != "" { + return email, true + } + if url := supplier.GetURL(); url != "" { + return url, true + } + for _, contact := range supplier.GetContacts() { + if email := contact.Email(); email != "" { + return email, true + } + } + return "", false +} - if result != "" { - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) - } +func getAuthorInfo(authors []sbom.GetAuthor) (string, bool) { + for _, author := range authors { + if email := author.GetEmail(); email != "" { + return email, true + } + if name := author.GetName(); name != "" { + return name, true + } + } + return "", false +} +func getToolInfo(tools []sbom.GetTool) (string, bool) { + for _, tool := range tools { + if name := tool.GetName(); name != "" { + return name, true } } - return newRecordStmt(SBOM_CREATOR, "SBOM Data Fields", result, score) + return "", false } func ntiaSbomCreatedTimestamp(doc sbom.Document) *record { - score := 0.0 + score := SCORE_ZERO result := doc.Spec().GetCreationTimestamp() if result != "" { _, err := time.Parse(time.RFC3339, result) if err != nil { - score = 0.0 + score = SCORE_ZERO } else { - score = 10.0 + score = SCORE_FULL } } return newRecordStmt(SBOM_TIMESTAMP, "SBOM Data Fields", result, score) @@ -239,7 +233,7 @@ func ntiaComponents(doc sbom.Document) []*record { records := []*record{} if len(doc.Components()) == 0 { - records = append(records, newRecordStmt(SBOM_COMPONENTS, "SBOM Data Fields", "absent", 0.0)) + records = append(records, newRecordStmt(SBOM_COMPONENTS, "SBOM Data Fields", "absent", SCORE_ZERO)) return records } @@ -260,118 +254,66 @@ func ntiaComponents(doc sbom.Document) []*record { func ntiaComponentName(component sbom.GetComponent) *record { if result := component.GetName(); result != "" { - return newRecordStmt(COMP_NAME, component.GetName(), result, 10.0) + return newRecordStmt(COMP_NAME, component.GetName(), result, SCORE_FULL) } - return newRecordStmt(COMP_NAME, component.GetName(), "", 0.0) + return newRecordStmt(COMP_NAME, component.GetName(), "", SCORE_ZERO) } func ntiaComponentCreator(doc sbom.Document, component sbom.GetComponent) *record { spec := doc.Spec().GetSpecType() - - if spec == "spdx" { - if supplier := component.Suppliers().GetEmail(); supplier != "" { - return newRecordStmt(PACK_SUPPLIER, component.GetName(), supplier, 10.0) - } - } else if spec == "cyclonedx" { - result := "" - score := 0.0 - - supplier := component.Suppliers() - if supplier != nil { - if supplier.GetEmail() != "" { - result = supplier.GetEmail() - score = 10.0 - } - - if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) - } - - if supplier.GetURL() != "" { - result = supplier.GetURL() - score = 10.0 - } - - if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) - } - - if supplier.GetContacts() != nil { - for _, contact := range supplier.GetContacts() { - if contact.Email() != "" { - result = contact.Email() - score = 10.0 - break - } - } - - if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) - } + result, score := "", SCORE_ZERO + + switch spec { + case "spdx": + if supplier := component.Suppliers(); supplier != nil { + if supplierResult, found := getSupplierInfo(supplier); found { + result = supplierResult + score = SCORE_FULL + break } } - - manufacturer := component.Manufacturer() - - if manufacturer != nil { - if manufacturer.GetEmail() != "" { - result = manufacturer.GetEmail() - score = 10.0 - } - - if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) - } - - if manufacturer.GetEmail() != "" { - result = manufacturer.GetURL() - score = 10.0 - } - - if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) + case "cyclonedx": + if supplier := component.Suppliers(); supplier != nil { + if supplierResult, found := getSupplierInfo(supplier); found { + result = supplierResult + score = SCORE_FULL + break } + } - if manufacturer.GetContacts() != nil { - for _, contact := range manufacturer.GetContacts() { - if contact.Email() != "" { - result = contact.Email() - score = 10.0 - break - } - } - - if result != "" { - return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) - } + if manufacturer := component.Manufacturer(); manufacturer != nil { + if manufacturerResult, found := getManufacturerInfo(manufacturer); found { + result = manufacturerResult + score = SCORE_FULL + break } } } - return newRecordStmt(COMP_CREATOR, component.GetName(), "", 0.0) + return newRecordStmt(COMP_CREATOR, component.GetName(), result, score) } func ntiaComponentVersion(component sbom.GetComponent) *record { result := component.GetVersion() if result != "" { - return newRecordStmt(COMP_VERSION, component.GetName(), result, 10.0) + return newRecordStmt(COMP_VERSION, component.GetName(), result, SCORE_FULL) } - return newRecordStmt(COMP_VERSION, component.GetName(), "", 0.0) + return newRecordStmt(COMP_VERSION, component.GetName(), "", SCORE_ZERO) } func ntiaComponentDependencies(doc sbom.Document, component sbom.GetComponent) *record { - result, score := "", 0.0 + result, score := "", SCORE_ZERO var results []string dependencies := doc.GetRelationships(component.GetID()) if dependencies == nil { - return newRecordStmt(COMP_DEPTH, component.GetName(), "no-relationships", 0.0) + return newRecordStmt(COMP_DEPTH, component.GetName(), "no-relationships", SCORE_ZERO) } for _, d := range dependencies { componentName := extractName(d) results = append(results, componentName) - score = 10.0 + score = SCORE_FULL } if results != nil { @@ -387,7 +329,7 @@ func ntiaComponentOtherUniqIDs(doc sbom.Document, component sbom.GetComponent) * spec := doc.Spec().GetSpecType() if spec == "spdx" { - result, score, totalElements, containPurlElement := "", 0.0, 0, 0 + result, score, totalElements, containPurlElement := "", SCORE_ZERO, 0, 0 if extRefs := component.ExternalReferences(); extRefs != nil { for _, extRef := range extRefs { @@ -399,34 +341,31 @@ func ntiaComponentOtherUniqIDs(doc sbom.Document, component sbom.GetComponent) * } } if containPurlElement != 0 { - score = (float64(containPurlElement) / float64(totalElements)) * 10.0 + score = (float64(containPurlElement) / float64(totalElements)) * SCORE_FULL x := fmt.Sprintf(":(%d/%d)", containPurlElement, totalElements) result = result + x } return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) } else if spec == "cyclonedx" { result := "" - score := 0.0 purl := component.GetPurls() if len(purl) > 0 { result = string(purl[0]) - score = 10.0 - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, SCORE_FULL) } cpes := component.GetCpes() if len(cpes) > 0 { result = string(cpes[0]) - score = 10.0 - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, score) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), result, SCORE_FULL) } - return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), "", 0.0) + return newRecordStmtOptional(COMP_OTHER_UNIQ_IDS, component.GetName(), "", SCORE_ZERO) } - return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetName(), "", 0.0) + return newRecordStmt(COMP_OTHER_UNIQ_IDS, component.GetName(), "", SCORE_ZERO) } diff --git a/pkg/compliance/ntia_test.go b/pkg/compliance/ntia_test.go index b037ccf..e2068a1 100644 --- a/pkg/compliance/ntia_test.go +++ b/pkg/compliance/ntia_test.go @@ -23,7 +23,8 @@ func createSpdxDummyDocumentNtia() sbom.Document { pack := sbom.NewComponent() pack.Version = "v0.7.1" - pack.Name = "core-js" + pack.Name = "tool-golang" + pack.ID = "github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1" supplier := sbom.Supplier{ Email: "hello@interlynk.io", @@ -34,6 +35,9 @@ func createSpdxDummyDocumentNtia() sbom.Document { RefType: "purl", } + var primary sbom.PrimaryComp + primary.Dependecies = 1 + var externalReferences []sbom.GetExternalReference externalReferences = append(externalReferences, extRef) pack.ExternalRefs = externalReferences @@ -41,18 +45,16 @@ func createSpdxDummyDocumentNtia() sbom.Document { var packages []sbom.GetComponent packages = append(packages, pack) - depend := sbom.Relation{ - From: "github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1", - To: "github/spdx/gordf@b735bd5aac89fe25cad4ef488a95bc00ea549edd", - } - var dependencies []sbom.GetRelation - dependencies = append(dependencies, depend) + relationships := make(map[string][]string) + relationships["github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1"] = append(relationships["github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1"], "github/spdx/gordf@b735bd5aac89fe25cad4ef488a95bc00ea549edd") + CompIDWithName["github/spdx/gordf@b735bd5aac89fe25cad4ef488a95bc00ea549edd"] = "gordf" doc := sbom.SpdxDoc{ - SpdxSpec: s, - Comps: packages, - SpdxTools: creators, - Rels: dependencies, + SpdxSpec: s, + Comps: packages, + SpdxTools: creators, + Dependencies: relationships, + PrimaryComponent: primary, } return doc } @@ -101,13 +103,24 @@ func TestNtiaSpdxSbomPass(t *testing.T) { id: "SBOM Data Fields", }, }, + { + name: "SbomDependency", + actual: ntiaSBOMDependency(doc), + expected: desiredNtia{ + score: 10.0, + result: "doc has 1 dependencies", + key: SBOM_DEPENDENCY, + id: "SBOM Data Fields", + }, + }, + { name: "ComponentCreator", actual: ntiaComponentCreator(doc, doc.Components()[0]), expected: desiredNtia{ score: 10.0, result: "hello@interlynk.io", - key: PACK_SUPPLIER, + key: COMP_CREATOR, id: doc.Components()[0].GetName(), }, }, @@ -117,7 +130,7 @@ func TestNtiaSpdxSbomPass(t *testing.T) { actual: ntiaComponentName(doc.Components()[0]), expected: desiredNtia{ score: 10.0, - result: "core-js", + result: "tool-golang", key: COMP_NAME, id: doc.Components()[0].GetName(), }, @@ -177,7 +190,8 @@ func createCdxDummyDocumentNtia() sbom.Document { comp := sbom.NewComponent() comp.Version = "v0.7.1" - comp.Name = "core-js" + comp.Name = "tool-golang" + comp.ID = "github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1" supplier := sbom.Supplier{ Email: "hello@interlynk.io", @@ -199,10 +213,20 @@ func createCdxDummyDocumentNtia() sbom.Document { var components []sbom.GetComponent components = append(components, comp) + relationships := make(map[string][]string) + relationships["github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1"] = append(relationships["github/spdx/tools-golang@9db247b854b9634d0109153d515fd1a9efd5a1b1"], "github/spdx/gordf@b735bd5aac89fe25cad4ef488a95bc00ea549edd") + + var primary sbom.PrimaryComp + primary.Dependecies = 1 + + CompIDWithName["github/spdx/gordf@b735bd5aac89fe25cad4ef488a95bc00ea549edd"] = "gordf" + doc := sbom.CdxDoc{ - CdxSpec: cdxSpec, - Comps: components, - CdxAuthors: authors, + CdxSpec: cdxSpec, + Comps: components, + CdxAuthors: authors, + Dependencies: relationships, + PrimaryComponent: primary, } return doc } @@ -244,6 +268,16 @@ func TestNtiaCdxSbomPass(t *testing.T) { id: "SBOM Data Fields", }, }, + { + name: "SbomDependency", + actual: ntiaSBOMDependency(doc), + expected: desiredNtia{ + score: 10.0, + result: "doc has 1 dependencies", + key: SBOM_DEPENDENCY, + id: "SBOM Data Fields", + }, + }, { name: "ComponentCreator", actual: ntiaComponentCreator(doc, doc.Components()[0]), @@ -251,7 +285,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { score: 10.0, result: "hello@interlynk.io", key: COMP_CREATOR, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -259,9 +293,9 @@ func TestNtiaCdxSbomPass(t *testing.T) { actual: ntiaComponentName(doc.Components()[0]), expected: desiredNtia{ score: 10.0, - result: "core-js", + result: "tool-golang", key: COMP_NAME, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -271,7 +305,7 @@ func TestNtiaCdxSbomPass(t *testing.T) { score: 10.0, result: "v0.7.1", key: COMP_VERSION, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -281,7 +315,17 @@ func TestNtiaCdxSbomPass(t *testing.T) { score: 10.0, result: "vivek", key: COMP_OTHER_UNIQ_IDS, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), + }, + }, + { + name: "ComponentDependencies", + actual: ntiaComponentDependencies(doc, doc.Components()[0]), + expected: desiredNtia{ + score: 10.0, + result: "gordf", + key: COMP_DEPTH, + id: doc.Components()[0].GetName(), }, }, } diff --git a/pkg/compliance/oct_test.go b/pkg/compliance/oct_test.go index 7a42ac4..2655a4b 100644 --- a/pkg/compliance/oct_test.go +++ b/pkg/compliance/oct_test.go @@ -72,6 +72,7 @@ func createDummyDocument() sbom.Document { } type desired struct { + name string score float64 result string key int @@ -87,6 +88,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSpec(doc), expected: desired{ + name: "octSpec", score: 10.0, result: "spdx", key: SBOM_SPEC, @@ -96,6 +98,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSbomName(doc), expected: desired{ + name: "octSbomName", score: 10.0, result: "nano", key: SBOM_NAME, @@ -105,6 +108,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSbomNamespace(doc), expected: desired{ + name: "octSbomNamespace", score: 10.0, result: "https://anchore.com/syft/dir/sbomqs-6ec18b03-96cb-4951-b299-929890c1cfc8", key: SBOM_NAMESPACE, @@ -114,6 +118,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSbomOrganization(doc), expected: desired{ + name: "octSbomOrganization", score: 10.0, result: "interlynk", key: SBOM_ORG, @@ -123,6 +128,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSbomComment(doc), expected: desired{ + name: "octSbomComment", score: 10.0, result: "this is a general sbom created using syft tool", key: SBOM_COMMENT, @@ -132,6 +138,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSbomTool(doc), expected: desired{ + name: "octSbomTool", score: 10.0, result: "syft", key: SBOM_TOOL, @@ -141,6 +148,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSbomLicense(doc), expected: desired{ + name: "octSbomLicense", score: 10.0, result: "cc0-1.0", key: SBOM_LICENSE, @@ -150,6 +158,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSpecVersion(doc), expected: desired{ + name: "octSpecVersion", score: 10.0, result: "SPDX-2.3", key: SBOM_SPEC_VERSION, @@ -159,6 +168,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octCreatedTimestamp(doc), expected: desired{ + name: "octCreatedTimestamp", score: 10.0, result: "2023-05-04T09:33:40Z", key: SBOM_TIMESTAMP, @@ -168,6 +178,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octSpecSpdxID(doc), expected: desired{ + name: "octSpecSpdxID", score: 10.0, result: "DOCUMENT", key: SBOM_SPDXID, @@ -178,6 +189,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octMachineFormat(doc), expected: desired{ + name: "octMachineFormat", score: 10.0, result: "spdx, json", key: SBOM_MACHINE_FORMAT, @@ -187,6 +199,7 @@ func TestOctSbomPass(t *testing.T) { { actual: octHumanFormat(doc), expected: desired{ + name: "octHumanFormat", score: 10.0, result: "json", key: SBOM_HUMAN_FORMAT, @@ -196,100 +209,111 @@ func TestOctSbomPass(t *testing.T) { { actual: octPackageName(doc.Components()[0]), expected: desired{ + name: "octPackageName", score: 10.0, result: "core-js", key: PACK_NAME, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageVersion(doc.Components()[0]), expected: desired{ + name: "octPackageVersion", score: 10.0, result: "v0.7.1", key: PACK_VERSION, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageSpdxID(doc.Components()[0]), expected: desired{ + name: "octPackageSpdxID", score: 10.0, result: "SPDXRef-npm-core-js-3.6.5", key: PACK_SPDXID, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageSupplier(doc.Components()[0]), expected: desired{ + name: "octPackageSupplier", score: 10.0, result: "vivekkumarsahu650@gmail.com", key: PACK_SUPPLIER, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageHash(doc.Components()[0]), expected: desired{ + name: "octPackageHash", score: 10.0, result: "ee1300ac533cebc2d070ce3765685d5f7fca2a5a78ca15068323f68ed63d4abf", key: PACK_HASH, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageExternalRefs(doc.Components()[0]), expected: desired{ + name: "octPackageExternalRefs", score: 10.0, result: "purl:(1/1)", key: PACK_EXT_REF, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageCopyright(doc.Components()[0]), expected: desired{ + name: "octPackageCopyright", score: 10.0, result: "Copyright 2001-2011 The Apache Software Foundation", key: PACK_COPYRIGHT, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageFileAnalyzed(doc.Components()[0]), expected: desired{ + name: "octPackageFileAnalyzed", score: 10.0, result: "yes", key: PACK_FILE_ANALYZED, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageConLicense(doc.Components()[0]), expected: desired{ + name: "octPackageConLicense", score: 10.0, result: "(LGPL-2.0-only OR LicenseRef-3)", key: PACK_LICENSE_CON, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageDecLicense(doc.Components()[0]), expected: desired{ + name: "octPackageDecLicense", score: 10.0, result: "(LGPL-2.0-only AND LicenseRef-3)", key: PACK_LICENSE_DEC, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { actual: octPackageDownloadURL(doc.Components()[0]), expected: desired{ + name: "octPackageDownloadURL", score: 10.0, result: "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", key: PACK_DOWNLOAD_URL, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, } @@ -485,7 +509,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "", key: PACK_NAME, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -494,7 +518,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "", key: PACK_VERSION, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -503,7 +527,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "", key: PACK_SPDXID, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -512,7 +536,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "", key: PACK_SUPPLIER, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -521,7 +545,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "", key: PACK_HASH, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -530,7 +554,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "cpe23Type", key: PACK_EXT_REF, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -539,7 +563,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "NOASSERTION", key: PACK_COPYRIGHT, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -548,7 +572,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "no", key: PACK_FILE_ANALYZED, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -557,7 +581,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "NONE", key: PACK_LICENSE_CON, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -566,7 +590,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "NOASSERTION", key: PACK_LICENSE_DEC, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, { @@ -575,7 +599,7 @@ func TestOctSbomFail(t *testing.T) { score: 0.0, result: "", key: PACK_DOWNLOAD_URL, - id: doc.Components()[0].GetID(), + id: doc.Components()[0].GetName(), }, }, } diff --git a/pkg/engine/score_test.go b/pkg/engine/score_test.go index 01d8638..ee61771 100644 --- a/pkg/engine/score_test.go +++ b/pkg/engine/score_test.go @@ -105,4 +105,4 @@ func TestProcessURL(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/pkg/sbom/cdx.go b/pkg/sbom/cdx.go index 23aa5d2..135f4e6 100644 --- a/pkg/sbom/cdx.go +++ b/pkg/sbom/cdx.go @@ -36,22 +36,22 @@ var ( ) type CdxDoc struct { - doc *cydx.BOM - format FileFormat - ctx context.Context - CdxSpec *Specs - Comps []GetComponent - CdxAuthors []GetAuthor - CdxTools []GetTool - rels []GetRelation - logs []string - lifecycles []string - supplier GetSupplier - manufacturer Manufacturer - compositions map[string]string - primaryComp primaryComp - dependencies map[string][]string - composition map[string]string + doc *cydx.BOM + format FileFormat + ctx context.Context + CdxSpec *Specs + Comps []GetComponent + CdxAuthors []GetAuthor + CdxTools []GetTool + rels []GetRelation + logs []string + lifecycles []string + supplier GetSupplier + manufacturer Manufacturer + compositions map[string]string + PrimaryComponent PrimaryComp + Dependencies map[string][]string + composition map[string]string } func newCDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Document, error) { @@ -91,8 +91,8 @@ func newCDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Documen return doc, err } -func (c CdxDoc) PrimaryComp() PrimaryComp { - return &c.primaryComp +func (c CdxDoc) PrimaryComp() GetPrimaryComp { + return &c.PrimaryComponent } func (c CdxDoc) Spec() Spec { @@ -132,7 +132,7 @@ func (c CdxDoc) Manufacturer() Manufacturer { } func (c CdxDoc) GetRelationships(componentID string) []string { - return c.dependencies[componentID] + return c.Dependencies[componentID] } func (c CdxDoc) GetComposition(componentID string) string { @@ -293,7 +293,7 @@ func copyC(cdxc *cydx.Component, c *CdxDoc) *Component { } } - if cdxc.BOMRef == c.primaryComp.id { + if cdxc.BOMRef == c.PrimaryComponent.ID { nc.isPrimary = true } @@ -520,10 +520,10 @@ func (c *CdxDoc) parsePrimaryCompAndRelationships() { return } - c.dependencies = make(map[string][]string) + c.Dependencies = make(map[string][]string) - c.primaryComp.present = true - c.primaryComp.id = c.doc.Metadata.Component.BOMRef + c.PrimaryComponent.Present = true + c.PrimaryComponent.ID = c.doc.Metadata.Component.BOMRef var totalDependencies int c.rels = []GetRelation{} @@ -533,20 +533,21 @@ func (c *CdxDoc) parsePrimaryCompAndRelationships() { nr := Relation{} nr.From = r.Ref nr.To = d - if r.Ref == c.primaryComp.id { - c.primaryComp.hasDependencies = true + if r.Ref == c.PrimaryComponent.ID { + c.PrimaryComponent.hasDependencies = true totalDependencies++ c.rels = append(c.rels, nr) - c.dependencies[c.primaryComp.id] = append(c.dependencies[c.primaryComp.id], d) + c.Dependencies[c.PrimaryComponent.ID] = append(c.Dependencies[c.PrimaryComponent.ID], d) } else { c.rels = append(c.rels, nr) - c.dependencies[r.Ref] = append(c.dependencies[r.Ref], d) + c.Dependencies[r.Ref] = append(c.Dependencies[r.Ref], d) } } } - c.primaryComp.dependecies = totalDependencies + c.PrimaryComponent.Dependecies = totalDependencies } +// nolint func (c *CdxDoc) parseComposition() { if c.doc.Metadata == nil { return @@ -562,6 +563,7 @@ func (c *CdxDoc) parseComposition() { } } +// nolint func compNormalise(compID string) string { switch cydx.CompositionAggregate(compID) { case cydx.CompositionAggregateComplete: diff --git a/pkg/sbom/document.go b/pkg/sbom/document.go index 2a4d122..50b9458 100644 --- a/pkg/sbom/document.go +++ b/pkg/sbom/document.go @@ -29,6 +29,6 @@ type Document interface { Manufacturer() Manufacturer Supplier() GetSupplier - PrimaryComp() PrimaryComp + PrimaryComp() GetPrimaryComp GetRelationships(string) []string } diff --git a/pkg/sbom/primarycomp.go b/pkg/sbom/primarycomp.go index b856dc7..b1ebfe2 100644 --- a/pkg/sbom/primarycomp.go +++ b/pkg/sbom/primarycomp.go @@ -14,31 +14,31 @@ package sbom -type PrimaryComp interface { - Present() bool - ID() string - Dependencies() int +type GetPrimaryComp interface { + IsPresent() bool + GetID() string + GetTotalNoOfDependencies() int } -type primaryComp struct { - present bool - id string - dependecies int +type PrimaryComp struct { + Present bool + ID string + Dependecies int hasDependencies bool } -func (pc *primaryComp) Present() bool { - return pc.present +func (pc *PrimaryComp) IsPresent() bool { + return pc.Present } -func (pc *primaryComp) ID() string { - return pc.id +func (pc *PrimaryComp) GetID() string { + return pc.ID } -func (pc *primaryComp) Dependencies() int { - return pc.dependecies +func (pc *PrimaryComp) GetTotalNoOfDependencies() int { + return pc.Dependecies } -func (pc *primaryComp) HasDependencies() bool { +func (pc *PrimaryComp) HasDependencies() bool { return pc.hasDependencies } diff --git a/pkg/sbom/spdx.go b/pkg/sbom/spdx.go index c7e0c6b..3770531 100644 --- a/pkg/sbom/spdx.go +++ b/pkg/sbom/spdx.go @@ -43,19 +43,19 @@ var ( ) type SpdxDoc struct { - doc *spdx.Document - format FileFormat - ctx context.Context - SpdxSpec *Specs - Comps []GetComponent - authors []GetAuthor - SpdxTools []GetTool - Rels []GetRelation - logs []string - primaryComp primaryComp - lifecycles string - dependencies map[string][]string - composition map[string]string + doc *spdx.Document + format FileFormat + ctx context.Context + SpdxSpec *Specs + Comps []GetComponent + authors []GetAuthor + SpdxTools []GetTool + Rels []GetRelation + logs []string + PrimaryComponent PrimaryComp + lifecycles string + Dependencies map[string][]string + composition map[string]string } func newSPDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Document, error) { @@ -97,8 +97,8 @@ func newSPDXDoc(ctx context.Context, f io.ReadSeeker, format FileFormat) (Docume return doc, err } -func (c SpdxDoc) PrimaryComp() PrimaryComp { - return &c.primaryComp +func (s SpdxDoc) PrimaryComp() GetPrimaryComp { + return &s.PrimaryComponent } func (s SpdxDoc) Spec() Spec { @@ -138,7 +138,7 @@ func (s SpdxDoc) Supplier() GetSupplier { } func (s SpdxDoc) GetRelationships(componentID string) []string { - return s.dependencies[componentID] + return s.Dependencies[componentID] } func (s SpdxDoc) GetComposition(componentID string) string { @@ -244,7 +244,7 @@ func (s *SpdxDoc) parseComps() { nc.DownloadLocation = sc.PackageDownloadLocation } - nc.isPrimary = s.primaryComp.id == string(sc.PackageSPDXIdentifier) + nc.isPrimary = s.PrimaryComponent.ID == string(sc.PackageSPDXIdentifier) fromRelsPresent := func(rels []GetRelation, id string) bool { for _, r := range rels { @@ -288,7 +288,7 @@ func (s *SpdxDoc) parseAuthors() { func (s *SpdxDoc) parsePrimaryCompAndRelationships() { s.Rels = []GetRelation{} - s.dependencies = make(map[string][]string) + s.Dependencies = make(map[string][]string) var err error var aBytes, bBytes []byte var primaryComponent string @@ -302,22 +302,17 @@ func (s *SpdxDoc) parsePrimaryCompAndRelationships() { continue } primaryComponent = string(bBytes) - s.primaryComp.id = primaryComponent - s.primaryComp.present = true + s.PrimaryComponent.ID = primaryComponent + s.PrimaryComponent.Present = true } } - // if s.primaryComp.present { for _, r := range s.doc.Relationships { if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDependsOn { aBytes, err = r.RefA.MarshalJSON() if err != nil { continue } - // bBytes, err = r.RefB.ElementRefID.MarshalJSON() - // if err != nil { - // continue - // } if string(aBytes) == primaryComponent { bBytes, err = r.RefB.MarshalJSON() @@ -332,105 +327,19 @@ func (s *SpdxDoc) parsePrimaryCompAndRelationships() { totalDependencies++ s.Rels = append(s.Rels, nr) - s.dependencies[primaryComponent] = append(s.dependencies[primaryComponent], string(bBytes)) + s.Dependencies[primaryComponent] = append(s.Dependencies[primaryComponent], string(bBytes)) } else { nr := Relation{ From: string(aBytes), To: string(bBytes), } - s.dependencies[string(aBytes)] = append(s.dependencies[string(aBytes)], string(bBytes)) + s.Dependencies[string(aBytes)] = append(s.Dependencies[string(aBytes)], string(bBytes)) s.Rels = append(s.Rels, nr) } } } - // } -} - -// func (s *SpdxDoc) parseComponentDepedencies() { -// s.Rels = []GetRelation{} -// s.dependencies = make(map[string][]string) -// var err error -// var aBytes, bBytes []byte - -// for _, r := range s.doc.Relationships { -// if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDependsOn { -// aBytes, err = r.RefA.MarshalJSON() -// if err != nil { -// continue -// } -// bBytes, err = r.RefB.ElementRefID.MarshalJSON() -// if err != nil { -// continue -// } - -// nr := Relation{ -// From: string(aBytes), -// To: string(bBytes), -// } -// s.Rels = append(s.Rels, nr) -// s.dependencies[string(aBytes)] = append(s.dependencies[string(aBytes)], string(bBytes)) -// } -// } -// } - -// func (s *SpdxDoc) parseRelsAndPrimaryComp() { -// s.Rels = []GetRelation{} -// var err error -// var aBytes, bBytes []byte -// var primaryComponent string -// var totalDependencies int - -// for _, r := range s.doc.Relationships { -// if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDescribe { -// bBytes, err = r.RefB.ElementRefID.MarshalJSON() -// if err != nil { -// continue -// } -// primaryComponent = string(bBytes) -// s.primaryComp.id = primaryComponent -// s.primaryComp.present = true -// } -// } -// // If no primary component found, return early -// if primaryComponent == "" { -// return -// } - -// for _, r := range s.doc.Relationships { -// if strings.ToUpper(r.Relationship) == spdx_common.TypeRelationshipDependsOn { -// aBytes, err = r.RefA.MarshalJSON() -// if err != nil { -// continue -// } -// bBytes, err = r.RefB.ElementRefID.MarshalJSON() -// if err != nil { -// continue -// } - -// if string(aBytes) == primaryComponent { -// bBytes, err = r.RefB.MarshalJSON() -// if err != nil { -// continue -// } - -// nr := Relation{ -// From: primaryComponent, -// To: string(bBytes), -// } -// totalDependencies++ - -// s.Rels = append(s.Rels, nr) -// } else { -// nr := Relation{ -// From: string(aBytes), -// To: string(bBytes), -// } -// s.Rels = append(s.Rels, nr) -// } -// } -// } -// s.primaryComp.dependecies = totalDependencies -// } + s.PrimaryComponent.Dependecies = totalDependencies +} // creationInfo.Creators.Tool // Also create for org: , creationInfo.Creators.Organization diff --git a/pkg/scorer/ntia.go b/pkg/scorer/ntia.go index 0c03c23..9a5f552 100644 --- a/pkg/scorer/ntia.go +++ b/pkg/scorer/ntia.go @@ -116,7 +116,7 @@ func compWithUniqIDCheck(d sbom.Document, c *check) score { func docWithDepedenciesCheck(d sbom.Document, c *check) score { s := newScoreFromCheck(c) - totalDependencies := d.PrimaryComp().Dependencies() + totalDependencies := d.PrimaryComp().GetTotalNoOfDependencies() if totalDependencies > 0 { s.setScore(10.0) } diff --git a/pkg/scorer/quality.go b/pkg/scorer/quality.go index 7da7aff..4ee96e9 100644 --- a/pkg/scorer/quality.go +++ b/pkg/scorer/quality.go @@ -231,7 +231,7 @@ func docWithCreatorCheck(d sbom.Document, c *check) score { func docWithPrimaryComponentCheck(d sbom.Document, c *check) score { s := newScoreFromCheck(c) - if d.PrimaryComp().Present() { + if d.PrimaryComp().IsPresent() { s.setScore(10.0) s.setDesc("primary component found") return *s