Skip to content

Commit

Permalink
Merge pull request #233 from kubescape/img-manifest
Browse files Browse the repository at this point in the history
add image manifest to scan summary
  • Loading branch information
refaelm92 committed Jun 4, 2024
2 parents 18e2633 + 2dfbf14 commit 5c843e1
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 27 deletions.
2 changes: 1 addition & 1 deletion adapters/mockplatform.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (m MockPlatform) SendStatus(ctx context.Context, _ int) error {
}

// SubmitCVE logs the given ID for CVE calculation
func (m MockPlatform) SubmitCVE(ctx context.Context, _ domain.CVEManifest, _ domain.CVEManifest) error {
func (m MockPlatform) SubmitCVE(ctx context.Context, _ domain.SBOM, _ domain.CVEManifest, _ domain.CVEManifest) error {
_, span := otel.Tracer("").Start(ctx, "MockPlatform.SubmitCVE")
defer span.End()
return nil
Expand Down
2 changes: 1 addition & 1 deletion adapters/mockplatform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ func TestMockPlatform_SendStatus(t *testing.T) {
func TestMockPlatform_SubmitCVE(t *testing.T) {
m := NewMockPlatform(true)
ctx := context.TODO()
err := m.SubmitCVE(ctx, domain.CVEManifest{}, domain.CVEManifest{})
err := m.SubmitCVE(ctx, domain.SBOM{}, domain.CVEManifest{}, domain.CVEManifest{})
assert.NoError(t, err)
}
8 changes: 6 additions & 2 deletions adapters/v1/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (a *BackendAdapter) reportFromContext(ctx context.Context) (*sysreport.Base
}

// SubmitCVE submits the given CVE to the platform
func (a *BackendAdapter) SubmitCVE(ctx context.Context, cve domain.CVEManifest, cvep domain.CVEManifest) error {
func (a *BackendAdapter) SubmitCVE(ctx context.Context, sbom domain.SBOM, cve domain.CVEManifest, cvep domain.CVEManifest) error {
ctx, span := otel.Tracer("").Start(ctx, "BackendAdapter.SubmitCVE")
defer span.End()
// retrieve timestamp from context
Expand Down Expand Up @@ -255,8 +255,12 @@ func (a *BackendAdapter) SubmitCVE(ctx context.Context, cve domain.CVEManifest,
vulnerabilities[i].Designators = finalReport.Designators
}

imageManifest, err := parseImageManifest(sbom)
if err != nil {
logger.L().Ctx(ctx).Warning("failed to parse image manifest from sbom", helpers.Error(err))
}
// add summary
finalReport.Summary, vulnerabilities = summarize(finalReport, vulnerabilities, workload, hasRelevancy)
finalReport.Summary, vulnerabilities = summarize(finalReport, vulnerabilities, workload, hasRelevancy, imageManifest)
finalReport.Summary.Context = armoContext

// split vulnerabilities to chunks
Expand Down
76 changes: 66 additions & 10 deletions adapters/v1/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package v1
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"github.com/armosec/armoapi-go/containerscan"
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -82,17 +85,17 @@ func TestBackendAdapter_GetCVEExceptions(t *testing.T) {
}
}

func fileToCVEManifest(path string) domain.CVEManifest {
var cve domain.CVEManifest
func fileToType[T any](path string) *T {
var t *T
b, err := os.ReadFile(path)
if err != nil {
panic(err)
}
err = json.Unmarshal(b, &cve)
err = json.Unmarshal(b, &t)
if err != nil {
panic(err)
}
return cve
return t
}

func TestBackendAdapter_SubmitCVE(t *testing.T) {
Expand All @@ -108,21 +111,21 @@ func TestBackendAdapter_SubmitCVE(t *testing.T) {
}{
{
name: "submit small cve",
cve: fileToCVEManifest("testdata/nginx-cve-small.json"),
cve: *fileToType[domain.CVEManifest]("testdata/nginx-cve-small.json"),
checkFullBody: true,
},
{
name: "submit big cve",
cve: fileToCVEManifest("testdata/nginx-cve.json"),
cve: *fileToType[domain.CVEManifest]("testdata/nginx-cve.json"),
},
{
name: "submit big cve with relevancy",
cve: fileToCVEManifest("testdata/nginx-cve.json"),
cvep: fileToCVEManifest("testdata/nginx-filtered-cve.json"),
cve: *fileToType[domain.CVEManifest]("testdata/nginx-cve.json"),
cvep: *fileToType[domain.CVEManifest]("testdata/nginx-filtered-cve.json"),
},
{
name: "submit small cve with exceptions",
cve: fileToCVEManifest("testdata/nginx-cve-small.json"),
cve: *fileToType[domain.CVEManifest]("testdata/nginx-cve-small.json"),
checkFullBodyWithException: true,
exceptions: []armotypes.VulnerabilityExceptionPolicy{{
PolicyType: "vulnerabilityExceptionPolicy",
Expand Down Expand Up @@ -190,13 +193,66 @@ func TestBackendAdapter_SubmitCVE(t *testing.T) {
ctx = context.WithValue(ctx, domain.TimestampKey{}, time.Now().Unix())
ctx = context.WithValue(ctx, domain.ScanIDKey{}, uuid.New().String())
ctx = context.WithValue(ctx, domain.WorkloadKey{}, domain.ScanCommand{})
if err := a.SubmitCVE(ctx, tt.cve, tt.cvep); (err != nil) != tt.wantErr {
if err := a.SubmitCVE(ctx, domain.SBOM{}, tt.cve, tt.cvep); (err != nil) != tt.wantErr {
t.Errorf("SubmitCVE() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

//go:embed testdata/nginx-sbom-metadata.json
var nginxSBOMMetadata []byte

func TestParseImageManifest(t *testing.T) {
tests := []struct {
name string
sbom domain.SBOM
expected *containerscan.ImageManifest
wantErr bool
}{
{
name: "empty sbom",
sbom: domain.SBOM{},
expected: nil,
},
{
name: "malformed metadata base64 config",
sbom: domain.SBOM{
Content: &v1beta1.SyftDocument{
SyftSource: v1beta1.SyftSource{
Metadata: []byte(`{
"config": "eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIs"
}`),
},
},
},
wantErr: true,
},
{
name: "valid sbom",
sbom: domain.SBOM{
Content: &v1beta1.SyftDocument{
SyftSource: v1beta1.SyftSource{
Metadata: nginxSBOMMetadata,
},
},
},
expected: fileToType[containerscan.ImageManifest]("testdata/nginx-image-manifest.json"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
imageManifest, err := parseImageManifest(tt.sbom)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, imageManifest)
}
})
}
}

func TestNewBackendAdapter(t *testing.T) {
type args struct {
accountID string
Expand Down
3 changes: 2 additions & 1 deletion adapters/v1/backend_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func incrementCounter(counter *int64, isGlobal, isIgnored bool) {
*counter++
}

func summarize(report v1.ScanResultReport, vulnerabilities []containerscan.CommonContainerVulnerabilityResult, workload domain.ScanCommand, hasRelevancy bool) (*containerscan.CommonContainerScanSummaryResult, []containerscan.CommonContainerVulnerabilityResult) {
func summarize(report v1.ScanResultReport, vulnerabilities []containerscan.CommonContainerVulnerabilityResult, workload domain.ScanCommand, hasRelevancy bool, imageManifest *containerscan.ImageManifest) (*containerscan.CommonContainerScanSummaryResult, []containerscan.CommonContainerVulnerabilityResult) {
summary := containerscan.CommonContainerScanSummaryResult{
Designators: report.Designators,
SeverityStats: containerscan.SeverityStats{},
Expand All @@ -166,6 +166,7 @@ func summarize(report v1.ScanResultReport, vulnerabilities []containerscan.Commo
JobIDs: workload.Session.JobIDs,
Timestamp: report.Timestamp,
HasRelevancyData: hasRelevancy,
ImageManifest: imageManifest,
}

imageInfo, err := armometadata.ImageTagToImageInfo(workload.ImageTagNormalized)
Expand Down
21 changes: 16 additions & 5 deletions adapters/v1/backend_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"io"
"net/http"
Expand Down Expand Up @@ -174,7 +175,12 @@ func TestGetCVEExceptionMatchCVENameFromList(t *testing.T) {
}
}

//go:embed testdata/nginx-image-manifest.json
var nginxImageManifest []byte

func Test_summarize(t *testing.T) {
var imageManifest containerscan.ImageManifest
assert.NoError(t, json.Unmarshal(nginxImageManifest, &imageManifest))
containerScanID := "9711c327-1a08-487e-b24a-72128712ef2d"
designators := identifiers.PortalDesignator{
DesignatorType: "Attributes",
Expand All @@ -201,6 +207,7 @@ func Test_summarize(t *testing.T) {
vulnerabilities []containerscan.CommonContainerVulnerabilityResult
workload domain.ScanCommand
hasRelevancy bool
imageManifest *containerscan.ImageManifest
}
tests := []struct {
name string
Expand Down Expand Up @@ -338,7 +345,8 @@ func Test_summarize(t *testing.T) {
JobIDs: jobIDs,
},
},
hasRelevancy: false,
hasRelevancy: false,
imageManifest: &imageManifest,
},
want: &containerscan.CommonContainerScanSummaryResult{
ClusterName: designators.Attributes["cluster"],
Expand Down Expand Up @@ -372,7 +380,8 @@ func Test_summarize(t *testing.T) {
{Name: "CVE-2017-18269"},
{Name: "CVE-2022-1292"},
},
WLID: wlid,
WLID: wlid,
ImageManifest: &imageManifest,
},
},
{
Expand Down Expand Up @@ -467,7 +476,8 @@ func Test_summarize(t *testing.T) {
JobIDs: jobIDs,
},
},
hasRelevancy: true,
hasRelevancy: true,
imageManifest: &imageManifest,
},
want: &containerscan.CommonContainerScanSummaryResult{
ClusterName: designators.Attributes["cluster"],
Expand Down Expand Up @@ -505,13 +515,14 @@ func Test_summarize(t *testing.T) {
{Name: "CVE-2017-18269"},
{Name: "CVE-2022-1292"},
},
WLID: wlid,
WLID: wlid,
ImageManifest: &imageManifest,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, _ := summarize(tt.args.report, tt.args.vulnerabilities, tt.args.workload, tt.args.hasRelevancy)
got, _ := summarize(tt.args.report, tt.args.vulnerabilities, tt.args.workload, tt.args.hasRelevancy, tt.args.imageManifest)
sort.Slice(got.SeveritiesStats, func(i, j int) bool {
return got.SeveritiesStats[i].Severity < got.SeveritiesStats[j].Severity
})
Expand Down
41 changes: 41 additions & 0 deletions adapters/v1/domain_to_armo.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,44 @@ func syftCoordinatesToCoordinates(c []v1beta1.SyftCoordinates) []containerscan.C
return coordinates

}

func parseImageManifest(sbom domain.SBOM) (*containerscan.ImageManifest, error) {
if sbom.Content == nil {
return nil, nil
}
var rawManifest source.ImageMetadata
if err := json.Unmarshal(sbom.Content.SyftSource.Metadata, &rawManifest); err != nil {
return nil, err
}

var config v1.ConfigFile
err := json.Unmarshal(rawManifest.RawConfig, &config)
if err != nil {
return nil, err
}

imageManifest := containerscan.ImageManifest{
Architecture: config.Architecture,
OS: config.OS,
Size: rawManifest.Size,
Layers: []containerscan.ESLayer{},
}

layerIndex := 0
for i, historyLayer := range config.History {
layerInfo := containerscan.ESLayer{
LayerInfo: &containerscan.LayerInfo{
CreatedBy: historyLayer.CreatedBy,
CreatedTime: &historyLayer.Created.Time,
LayerOrder: i,
},
}
if !historyLayer.EmptyLayer && layerIndex < len(rawManifest.Layers) {
layerInfo.LayerHash = rawManifest.Layers[layerIndex].Digest
layerInfo.Size = rawManifest.Layers[layerIndex].Size
layerIndex++
}
imageManifest.Layers = append(imageManifest.Layers, layerInfo)
}
return &imageManifest, nil
}
Loading

0 comments on commit 5c843e1

Please sign in to comment.