diff --git a/pkg/aws/ecr.go b/pkg/aws/ecr.go index cdfe12b..95a2aba 100644 --- a/pkg/aws/ecr.go +++ b/pkg/aws/ecr.go @@ -8,6 +8,7 @@ import ( // ECRSVC is a wrapper for ECR Image Scan API calls type ECRSVC interface { GetECRImagesWithTag(tag string) (map[string][]*ecr.ImageDetail, error) + GetECRImageScanFindings(*ecr.ImageDetail) map[string]int64 } // GetECRImagesWithTag finds all ECR images with a given tag. If no tag specified, all tagged images are returned @@ -66,3 +67,22 @@ func (client *Client) GetECRImagesWithTag(tag string) (map[string][]*ecr.ImageDe return images, nil } + +func (client *Client) GetECRImageScanFindings(image *ecr.ImageDetail) map[string]int64 { + if image != nil { + scanFindings, err := client.ECR.DescribeImageScanFindings(&ecr.DescribeImageScanFindingsInput{ + ImageId: &ecr.ImageIdentifier{ + ImageDigest: image.ImageDigest, + }, + RegistryId: image.RegistryId, + RepositoryName: image.RepositoryName, + }) + if err != nil { + return nil + } + if scanFindings != nil && scanFindings.ImageScanStatus != nil { + return aws.Int64ValueMap(scanFindings.ImageScanFindings.FindingSeverityCounts) + } + } + return nil +} diff --git a/pkg/aws/ecr_test.go b/pkg/aws/ecr_test.go index 0d39260..5516bf3 100644 --- a/pkg/aws/ecr_test.go +++ b/pkg/aws/ecr_test.go @@ -223,3 +223,59 @@ func TestClient_GetECRImagesWithTag(t *testing.T) { }) } } + +func TestClient_GetECRImageScanFindings(t *testing.T) { + testCases := []struct { + name string + image *ecr.ImageDetail + describeImageScanFindingsOutput *ecr.DescribeImageScanFindingsOutput + scanFindingsMap map[string]int64 + }{ + { + name: "test case with empty image details", + image: nil, + scanFindingsMap: nil, + describeImageScanFindingsOutput: nil, + }, { + name: "test case with valid image details", + image: &ecr.ImageDetail{ + ImageTags: aws.StringSlice([]string{"prod-canary"}), + RegistryId: aws.String("012345678910"), + RepositoryName: aws.String("app/web-server"), + ImageDigest: aws.String("prod-canary-image-digest"), + }, + describeImageScanFindingsOutput: &ecr.DescribeImageScanFindingsOutput{ + ImageScanStatus: &ecr.ImageScanStatus{ + Status: aws.String("COMPLETE"), + }, + ImageScanFindings: &ecr.ImageScanFindings{ + FindingSeverityCounts: map[string]*int64{ + "MEDIUM": aws.Int64(1), + "CRITICAL": aws.Int64(1), + }, + }, + }, + scanFindingsMap: map[string]int64{ + "MEDIUM": 1, + "CRITICAL": 1, + }, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockECRAPI := mocks.NewMockECRAPI(mockCtrl) + if test.image != nil { + mockECRAPI.EXPECT().DescribeImageScanFindings(gomock.Any()).Return(test.describeImageScanFindingsOutput, nil).MaxTimes(1) + } + client := &Client{ + ECR: mockECRAPI, + } + got := client.GetECRImageScanFindings(test.image) + if !reflect.DeepEqual(got, test.scanFindingsMap) { + t.Errorf("GetECRImageScanFindings() got = %v, want %v", got, test.scanFindingsMap) + } + }) + } +} diff --git a/pkg/cloudig/ecrscan.go b/pkg/cloudig/ecrscan.go index cc8642d..69ff2c4 100644 --- a/pkg/cloudig/ecrscan.go +++ b/pkg/cloudig/ecrscan.go @@ -1,6 +1,7 @@ package cloudig import ( + "strings" "time" awslocal "github.com/Optum/cloudig/pkg/aws" @@ -60,33 +61,39 @@ func (report *ImageScanReports) GetReport(client awslocal.APIs, comments []Comme for repo, imageList := range images { // If a tag was specified there should only be one image returned per repo if report.Flags.Tag != "" && len(imageList) == 1 { - imageURI := repo + ":" + report.Flags.Tag - scanReport := ImageScanFindings{ - AccountID: accountID, - ImageDigest: aws.StringValue(imageList[0].ImageDigest), - ImageTag: report.Flags.Tag, - RepositoryName: aws.StringValue(imageList[0].RepositoryName), - ImageFindingsCount: convertScanFindings(imageList[0]), - Comments: getComments(comments, accountID, findingTypeECRScan, imageURI), - Region: report.Flags.Region, + scanFindingCountMap := convertScanFindings(imageList[0], client) + if len(scanFindingCountMap) > 0 { + imageURI := repo + ":" + report.Flags.Tag + scanReport := ImageScanFindings{ + AccountID: accountID, + ImageDigest: aws.StringValue(imageList[0].ImageDigest), + ImageTag: report.Flags.Tag, + RepositoryName: aws.StringValue(imageList[0].RepositoryName), + ImageFindingsCount: scanFindingCountMap, + Comments: getComments(comments, accountID, findingTypeECRScan, imageURI), + Region: report.Flags.Region, + } + report.Findings = append(report.Findings, scanReport) } - report.Findings = append(report.Findings, scanReport) + } else { // Create finding for each tag for _, image := range imageList { - for _, tag := range aws.StringValueSlice(image.ImageTags) { - imageURI := repo + ":" + tag + scanFindingCountMap := convertScanFindings(image, client) + if len(scanFindingCountMap) > 0 { + imageURI := repo + ":" + aws.StringValueSlice(image.ImageTags)[0] scanReport := ImageScanFindings{ AccountID: accountID, ImageDigest: aws.StringValue(image.ImageDigest), - ImageTag: tag, + ImageTag: strings.Join(aws.StringValueSlice(image.ImageTags), ","), RepositoryName: aws.StringValue(image.RepositoryName), - ImageFindingsCount: convertScanFindings(image), + ImageFindingsCount: scanFindingCountMap, Comments: getComments(comments, accountID, findingTypeECRScan, imageURI), Region: report.Flags.Region, } report.Findings = append(report.Findings, scanReport) } + } } } @@ -95,9 +102,16 @@ func (report *ImageScanReports) GetReport(client awslocal.APIs, comments []Comme return nil } -func convertScanFindings(image *ecr.ImageDetail) map[string]int64 { +func convertScanFindings(image *ecr.ImageDetail, client awslocal.APIs) map[string]int64 { if image != nil && image.ImageScanStatus != nil && aws.StringValue(image.ImageScanStatus.Status) == "COMPLETE" { return aws.Int64ValueMap(image.ImageScanFindingsSummary.FindingSeverityCounts) + } else { + /** + * we are unable to get the FindingSeverityCounts, ImageScanStatus for enhanced scanning. + * it is known bug in aws side, so we are calling describe-images-scan-findings to get the details. + * we will reverse these , once aws fix issue in describe-image + **/ + return client.GetECRImageScanFindings(image) + } - return nil } diff --git a/pkg/mocks/mock_aws.go b/pkg/mocks/mock_aws.go index 30bee07..dd3b39b 100644 --- a/pkg/mocks/mock_aws.go +++ b/pkg/mocks/mock_aws.go @@ -92,6 +92,14 @@ func (m *MockAPIs) GetECRImagesWithTag(arg0 string) (map[string][]*ecr.ImageDeta return ret0, ret1 } +// GetECRImageScanFindings mocks base method +func (m *MockAPIs) GetECRImageScanFindings(arg0 *ecr.ImageDetail) map[string]int64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetECRImageScanFindings", arg0) + ret0, _ := ret[0].(map[string]int64) + return ret0 +} + // GetECRImagesWithTag indicates an expected call of GetECRImagesWithTag func (mr *MockAPIsMockRecorder) GetECRImagesWithTag(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper()