Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ntia compliance report #286

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions Compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,21 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

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

We should also denotes field which are mandatory vs optional with an *

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done


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 |
| :--- | :--- |:--- | :--- | :--- | :--- |
| 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.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 |
| Practices and Processes | 3.1 | `Depth` | dependencies, compositions | relationships | optional |
| | 3.2 | `Known Unknowns` | | | optional |
3 changes: 2 additions & 1 deletion cmd/compliance.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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.Bsi, _ = cmd.Flags().GetBool("bsi")
engParams.Oct, _ = cmd.Flags().GetBool("oct")
Expand All @@ -96,7 +97,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")
Expand Down
127 changes: 65 additions & 62 deletions pkg/compliance/bsi.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package compliance

import (
"context"
"fmt"
"strings"

"github.com/interlynk-io/sbomqs/pkg/logger"
Expand Down Expand Up @@ -67,6 +68,7 @@ const (
PACK_COPYRIGHT
COMP_DEPTH
SBOM_MACHINE_FORMAT
SBOM_DEPENDENCY
SBOM_HUMAN_FORMAT
SBOM_BUILD_INFO
SBOM_DELIVERY_TIME
Expand Down Expand Up @@ -160,40 +162,25 @@ func bsiBuildPhase(doc sbom.Document) *record {
}

func bsiSbomDepth(doc sbom.Document) *record {
if !doc.PrimaryComponent() {
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().GetTotalNoOfDependencies()

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 dependencies", totalDependencies)

return newRecordStmt(SBOM_DEPTH, "doc", "non-compliant", 0.0)
return newRecordStmt(SBOM_DEPTH, "doc", result, score)
}

func bsiCreator(doc sbom.Document) *record {
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
}
Expand Down Expand Up @@ -305,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))
Expand All @@ -324,29 +315,44 @@ func bsiComponents(doc sbom.Document) []*record {
return records
}

func bsiComponentDepth(component sbom.GetComponent) *record {
if !component.HasRelationShips() {
return newRecordStmt(COMP_DEPTH, component.GetID(), "no-relationships", 0.0)
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.GetID(), "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.GetID(), "unattested-has-relationships", 5.0)
if fResults != nil {
result = strings.Join(fResults, ", ")
} else {
result += "no-relationships"
}

return newRecordStmt(COMP_DEPTH, component.GetID(), "non-compliant", 0.0)
return newRecordStmt(COMP_DEPTH, component.GetName(), result, score)
}

func bsiComponentLicense(component sbom.GetComponent) *record {
licenses := component.Licenses()
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.GetID(), "not-compliant", score)
return newRecordStmt(COMP_LICENSE, component.GetName(), "not-compliant", score)
}

var spdx, aboutcode, custom int
Expand All @@ -372,15 +378,12 @@ 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.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 {
Expand All @@ -392,51 +395,51 @@ 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 {
result := ""
score := 0.0

purl := component.Purls()
purl := component.GetPurls()

if len(purl) > 0 {
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.Cpes()
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.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 {
Expand All @@ -454,27 +457,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 {
Expand All @@ -489,7 +492,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() != "" {
Expand All @@ -498,7 +501,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 {
Expand All @@ -511,7 +514,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)
}
}
}
Expand All @@ -525,7 +528,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() != "" {
Expand All @@ -534,7 +537,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 {
Expand All @@ -547,10 +550,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)
}
Loading
Loading