Skip to content

Commit adbc480

Browse files
authored
fix(reporter/sbom): add WindowsKB to SBOM output (#2454)
1 parent 173aacf commit adbc480

4 files changed

Lines changed: 290 additions & 0 deletions

File tree

reporter/sbom/cyclonedx.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,20 @@ func osToCdxComponent(r models.ScanResult) *cdx.Component {
137137
Value: r.RunningKernel.Version,
138138
})
139139
}
140+
if r.WindowsKB != nil {
141+
for _, kb := range r.WindowsKB.Applied {
142+
props = append(props, cdx.Property{
143+
Name: "future-architect:vuls:WindowsKB:Applied",
144+
Value: kb,
145+
})
146+
}
147+
for _, kb := range r.WindowsKB.Unapplied {
148+
props = append(props, cdx.Property{
149+
Name: "future-architect:vuls:WindowsKB:Unapplied",
150+
Value: kb,
151+
})
152+
}
153+
}
140154
return &cdx.Component{
141155
BOMRef: uuid.NewString(),
142156
Type: cdx.ComponentTypeOS,

reporter/sbom/cyclonedx_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package sbom_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
cdx "github.com/CycloneDX/cyclonedx-go"
8+
"github.com/google/go-cmp/cmp"
9+
"github.com/google/go-cmp/cmp/cmpopts"
10+
11+
"github.com/future-architect/vuls/models"
12+
"github.com/future-architect/vuls/reporter/sbom"
13+
)
14+
15+
func TestToCycloneDX(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
args models.ScanResult
19+
want *cdx.BOM
20+
}{
21+
{
22+
name: "windows",
23+
args: models.ScanResult{
24+
Family: "windows",
25+
Release: "Windows Server 2022",
26+
ReportedAt: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
27+
ReportedVersion: "v0.38.5",
28+
ReportedRevision: "build-20260311_001506_6827f2d",
29+
WindowsKB: &models.WindowsKB{
30+
Applied: []string{"5025221", "5022282"},
31+
Unapplied: []string{"5026370"},
32+
},
33+
},
34+
want: &cdx.BOM{
35+
XMLNS: "http://cyclonedx.org/schema/bom/1.6",
36+
JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json",
37+
BOMFormat: "CycloneDX",
38+
SpecVersion: cdx.SpecVersion1_6,
39+
Version: 1,
40+
Metadata: &cdx.Metadata{
41+
Timestamp: "2025-01-01T00:00:00Z",
42+
Tools: &cdx.ToolsChoice{
43+
Components: &[]cdx.Component{
44+
{
45+
Type: cdx.ComponentTypeApplication,
46+
Group: "future-architect",
47+
Name: "vuls",
48+
Version: "v0.38.5-build-20260311_001506_6827f2d",
49+
},
50+
},
51+
},
52+
Component: &cdx.Component{
53+
Type: cdx.ComponentTypeOS,
54+
Name: "windows",
55+
Version: "Windows Server 2022",
56+
Properties: &[]cdx.Property{
57+
{Name: "future-architect:vuls:Type", Value: "windows"},
58+
{Name: "future-architect:vuls:WindowsKB:Applied", Value: "5025221"},
59+
{Name: "future-architect:vuls:WindowsKB:Applied", Value: "5022282"},
60+
{Name: "future-architect:vuls:WindowsKB:Unapplied", Value: "5026370"},
61+
},
62+
},
63+
},
64+
Components: new([]cdx.Component),
65+
Dependencies: &[]cdx.Dependency{},
66+
Vulnerabilities: &[]cdx.Vulnerability{},
67+
},
68+
},
69+
{
70+
name: "non-windows",
71+
args: models.ScanResult{
72+
Family: "centos",
73+
Release: "7",
74+
ReportedAt: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
75+
ReportedVersion: "v0.38.5",
76+
ReportedRevision: "build-20260311_001506_6827f2d",
77+
},
78+
want: &cdx.BOM{
79+
XMLNS: "http://cyclonedx.org/schema/bom/1.6",
80+
JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json",
81+
BOMFormat: "CycloneDX",
82+
SpecVersion: cdx.SpecVersion1_6,
83+
Version: 1,
84+
Metadata: &cdx.Metadata{
85+
Timestamp: "2025-01-01T00:00:00Z",
86+
Tools: &cdx.ToolsChoice{
87+
Components: &[]cdx.Component{
88+
{
89+
Type: cdx.ComponentTypeApplication,
90+
Group: "future-architect",
91+
Name: "vuls",
92+
Version: "v0.38.5-build-20260311_001506_6827f2d",
93+
},
94+
},
95+
},
96+
Component: &cdx.Component{
97+
Type: cdx.ComponentTypeOS,
98+
Name: "centos",
99+
Version: "7",
100+
Properties: &[]cdx.Property{
101+
{Name: "future-architect:vuls:Type", Value: "centos"},
102+
},
103+
},
104+
},
105+
Components: new([]cdx.Component),
106+
Dependencies: &[]cdx.Dependency{},
107+
Vulnerabilities: &[]cdx.Vulnerability{},
108+
},
109+
},
110+
}
111+
for _, tt := range tests {
112+
t.Run(tt.name, func(t *testing.T) {
113+
got := sbom.ToCycloneDX(tt.args)
114+
115+
opts := cmp.Options{
116+
cmpopts.IgnoreFields(cdx.BOM{}, "SerialNumber"),
117+
cmpopts.IgnoreFields(cdx.Component{}, "BOMRef"),
118+
}
119+
if diff := cmp.Diff(tt.want, got, opts...); diff != "" {
120+
t.Errorf("ToCycloneDX() mismatch (-want +got):\n%s", diff)
121+
}
122+
})
123+
}
124+
}

reporter/sbom/spdx.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ func osToSpdxPackage(r models.ScanResult) spdx.Package {
9090
if r.RunningKernel.Version != "" {
9191
annotations = appendAnnotation(annotations, "RunningKernelVersion", r.RunningKernel.Version, r.ReportedAt)
9292
}
93+
if r.WindowsKB != nil {
94+
for _, kb := range r.WindowsKB.Applied {
95+
annotations = appendAnnotation(annotations, "WindowsKB:Applied", kb, r.ReportedAt)
96+
}
97+
for _, kb := range r.WindowsKB.Unapplied {
98+
annotations = appendAnnotation(annotations, "WindowsKB:Unapplied", kb, r.ReportedAt)
99+
}
100+
}
93101

94102
return spdx.Package{
95103
PackageSPDXIdentifier: generateSDPXIDentifier(elementOperatingSystem),

reporter/sbom/spdx_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package sbom_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
"github.com/spdx/tools-golang/spdx"
10+
"github.com/spdx/tools-golang/spdx/v2/common"
11+
12+
"github.com/future-architect/vuls/models"
13+
"github.com/future-architect/vuls/reporter/sbom"
14+
)
15+
16+
func TestToSPDX(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
args models.ScanResult
20+
want spdx.Document
21+
}{
22+
{
23+
name: "windows",
24+
args: models.ScanResult{
25+
Family: "windows",
26+
Release: "Windows Server 2022",
27+
ReportedAt: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
28+
ReportedVersion: "v0.38.5",
29+
ReportedRevision: "build-20260311_001506_6827f2d",
30+
WindowsKB: &models.WindowsKB{
31+
Applied: []string{"5025221", "5022282"},
32+
Unapplied: []string{"5026370"},
33+
},
34+
},
35+
want: spdx.Document{
36+
SPDXVersion: spdx.Version,
37+
DataLicense: spdx.DataLicense,
38+
SPDXIdentifier: "DOCUMENT",
39+
DocumentName: "windows",
40+
CreationInfo: &spdx.CreationInfo{
41+
Creators: []common.Creator{
42+
{Creator: "future-architect", CreatorType: "Organization"},
43+
{Creator: "test-tool", CreatorType: "Tool"},
44+
},
45+
Created: "2025-01-01T00:00:00Z",
46+
},
47+
Packages: []*spdx.Package{
48+
{
49+
PackageName: "windows",
50+
PackageVersion: "Windows Server 2022",
51+
PackageDownloadLocation: "NONE",
52+
PrimaryPackagePurpose: "OPERATING-SYSTEM",
53+
Annotations: []spdx.Annotation{
54+
{
55+
Annotator: spdx.Annotator{Annotator: "future-architect:vuls", AnnotatorType: "Tool"},
56+
AnnotationDate: "2025-01-01T00:00:00Z",
57+
AnnotationType: "Other",
58+
AnnotationComment: "OsFamily: windows",
59+
},
60+
{
61+
Annotator: spdx.Annotator{Annotator: "future-architect:vuls", AnnotatorType: "Tool"},
62+
AnnotationDate: "2025-01-01T00:00:00Z",
63+
AnnotationType: "Other",
64+
AnnotationComment: "WindowsKB:Applied: 5022282",
65+
},
66+
{
67+
Annotator: spdx.Annotator{Annotator: "future-architect:vuls", AnnotatorType: "Tool"},
68+
AnnotationDate: "2025-01-01T00:00:00Z",
69+
AnnotationType: "Other",
70+
AnnotationComment: "WindowsKB:Applied: 5025221",
71+
},
72+
{
73+
Annotator: spdx.Annotator{Annotator: "future-architect:vuls", AnnotatorType: "Tool"},
74+
AnnotationDate: "2025-01-01T00:00:00Z",
75+
AnnotationType: "Other",
76+
AnnotationComment: "WindowsKB:Unapplied: 5026370",
77+
},
78+
},
79+
},
80+
},
81+
Relationships: []*spdx.Relationship{
82+
{Relationship: "DESCRIBES"},
83+
},
84+
},
85+
},
86+
{
87+
name: "non-windows",
88+
args: models.ScanResult{
89+
Family: "centos",
90+
Release: "7",
91+
ReportedAt: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
92+
ReportedVersion: "v0.38.5",
93+
ReportedRevision: "build-20260311_001506_6827f2d",
94+
},
95+
want: spdx.Document{
96+
SPDXVersion: spdx.Version,
97+
DataLicense: spdx.DataLicense,
98+
SPDXIdentifier: "DOCUMENT",
99+
DocumentName: "centos",
100+
CreationInfo: &spdx.CreationInfo{
101+
Creators: []common.Creator{
102+
{Creator: "future-architect", CreatorType: "Organization"},
103+
{Creator: "test-tool", CreatorType: "Tool"},
104+
},
105+
Created: "2025-01-01T00:00:00Z",
106+
},
107+
Packages: []*spdx.Package{
108+
{
109+
PackageName: "centos",
110+
PackageVersion: "7",
111+
PackageDownloadLocation: "NONE",
112+
PrimaryPackagePurpose: "OPERATING-SYSTEM",
113+
Annotations: []spdx.Annotation{
114+
{
115+
Annotator: spdx.Annotator{Annotator: "future-architect:vuls", AnnotatorType: "Tool"},
116+
AnnotationDate: "2025-01-01T00:00:00Z",
117+
AnnotationType: "Other",
118+
AnnotationComment: "OsFamily: centos",
119+
},
120+
},
121+
},
122+
},
123+
Relationships: []*spdx.Relationship{
124+
{Relationship: "DESCRIBES"},
125+
},
126+
},
127+
},
128+
}
129+
for _, tt := range tests {
130+
t.Run(tt.name, func(t *testing.T) {
131+
got := sbom.ToSPDX(tt.args, "test-tool")
132+
133+
opts := cmp.Options{
134+
cmpopts.IgnoreUnexported(spdx.Package{}),
135+
cmpopts.IgnoreFields(spdx.Document{}, "DocumentNamespace"),
136+
cmpopts.IgnoreFields(spdx.Package{}, "PackageSPDXIdentifier"),
137+
cmpopts.IgnoreFields(spdx.Relationship{}, "RefA", "RefB"),
138+
}
139+
if diff := cmp.Diff(tt.want, got, opts...); diff != "" {
140+
t.Errorf("ToSPDX() mismatch (-want +got):\n%s", diff)
141+
}
142+
})
143+
}
144+
}

0 commit comments

Comments
 (0)