From 8f9a458fbe98d519edb1681d46827060c19c27ce Mon Sep 17 00:00:00 2001 From: Victor Dorneanu Date: Thu, 22 Aug 2024 16:38:07 +0200 Subject: [PATCH] feat(scanner): Implement NVD scanner using generated graphql client (#159) * Implement NVD scanner * Add graphql client * Implement NVD scanner using generated graphql client * Automatic application of license header * Fix indentation * Better naming * Automatic application of license header * Remove e2e tests for now --------- Co-authored-by: Victor Dorneanu Co-authored-by: License Bot Co-authored-by: David Rochow --- scanner/nvd/README.md | 5 + scanner/nvd/client/generated.go | 504 ++++++++++++++++++ scanner/nvd/client/genqlient.yaml | 11 + .../nvd/client/queries/issue_create.graphql | 14 + .../queries/issuerepository_create.graphql | 13 + .../queries/issuerepository_query.graphql | 19 + .../nvd/client/queries/issues_query.graphql | 20 + .../queries/issuevariant_create.graphql | 13 + scanner/nvd/go.mod | 44 +- scanner/nvd/go.sum | 142 +++++ scanner/nvd/main.go | 76 ++- scanner/nvd/models/models.go | 365 +++++++++++++ scanner/nvd/processor/config.go | 10 + scanner/nvd/processor/processor.go | 120 +++++ scanner/nvd/scanner/config.go | 11 + scanner/nvd/scanner/scanner.go | 116 ++++ scanner/nvd/test/e2e/e2e_suite_test.go | 32 ++ scanner/nvd/test/e2e/processor_test.go | 91 ++++ 18 files changed, 1603 insertions(+), 3 deletions(-) create mode 100644 scanner/nvd/README.md create mode 100644 scanner/nvd/client/generated.go create mode 100644 scanner/nvd/client/genqlient.yaml create mode 100644 scanner/nvd/client/queries/issue_create.graphql create mode 100644 scanner/nvd/client/queries/issuerepository_create.graphql create mode 100644 scanner/nvd/client/queries/issuerepository_query.graphql create mode 100644 scanner/nvd/client/queries/issues_query.graphql create mode 100644 scanner/nvd/client/queries/issuevariant_create.graphql create mode 100644 scanner/nvd/go.sum create mode 100644 scanner/nvd/models/models.go create mode 100644 scanner/nvd/processor/config.go create mode 100644 scanner/nvd/processor/processor.go create mode 100644 scanner/nvd/scanner/config.go create mode 100644 scanner/nvd/scanner/scanner.go create mode 100644 scanner/nvd/test/e2e/e2e_suite_test.go create mode 100644 scanner/nvd/test/e2e/processor_test.go diff --git a/scanner/nvd/README.md b/scanner/nvd/README.md new file mode 100644 index 00000000..a786ee81 --- /dev/null +++ b/scanner/nvd/README.md @@ -0,0 +1,5 @@ +# NVD Scanner +## Description +tbd + +## Testing diff --git a/scanner/nvd/client/generated.go b/scanner/nvd/client/generated.go new file mode 100644 index 00000000..2d8591c8 --- /dev/null +++ b/scanner/nvd/client/generated.go @@ -0,0 +1,504 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by github.com/Khan/genqlient, DO NOT EDIT. + +package client + +import ( + "context" + + "github.com/Khan/genqlient/graphql" +) + +// CreateIssueRepositoryResponse is returned by CreateIssueRepository on success. +type CreateIssueRepositoryResponse struct { + CreateIssueRepository *IssueRepository `json:"createIssueRepository"` +} + +// GetCreateIssueRepository returns CreateIssueRepositoryResponse.CreateIssueRepository, and is useful for accessing the field via an interface. +func (v *CreateIssueRepositoryResponse) GetCreateIssueRepository() *IssueRepository { + return v.CreateIssueRepository +} + +// CreateIssueResponse is returned by CreateIssue on success. +type CreateIssueResponse struct { + CreateIssue *Issue `json:"createIssue"` +} + +// GetCreateIssue returns CreateIssueResponse.CreateIssue, and is useful for accessing the field via an interface. +func (v *CreateIssueResponse) GetCreateIssue() *Issue { return v.CreateIssue } + +// CreateIssueVariantResponse is returned by CreateIssueVariant on success. +type CreateIssueVariantResponse struct { + CreateIssueVariant *IssueVariant `json:"createIssueVariant"` +} + +// GetCreateIssueVariant returns CreateIssueVariantResponse.CreateIssueVariant, and is useful for accessing the field via an interface. +func (v *CreateIssueVariantResponse) GetCreateIssueVariant() *IssueVariant { + return v.CreateIssueVariant +} + +// GetIssueRepositoriesResponse is returned by GetIssueRepositories on success. +type GetIssueRepositoriesResponse struct { + IssueRepositories *IssueRepositoryConnection `json:"IssueRepositories"` +} + +// GetIssueRepositories returns GetIssueRepositoriesResponse.IssueRepositories, and is useful for accessing the field via an interface. +func (v *GetIssueRepositoriesResponse) GetIssueRepositories() *IssueRepositoryConnection { + return v.IssueRepositories +} + +// GetIssuesResponse is returned by GetIssues on success. +type GetIssuesResponse struct { + Issues *IssueConnection `json:"Issues"` +} + +// GetIssues returns GetIssuesResponse.Issues, and is useful for accessing the field via an interface. +func (v *GetIssuesResponse) GetIssues() *IssueConnection { return v.Issues } + +// Issue includes the requested fields of the GraphQL type Issue. +type Issue struct { + Id string `json:"id"` + PrimaryName string `json:"primaryName"` + Description string `json:"description"` + Type IssueTypes `json:"type"` +} + +// GetId returns Issue.Id, and is useful for accessing the field via an interface. +func (v *Issue) GetId() string { return v.Id } + +// GetPrimaryName returns Issue.PrimaryName, and is useful for accessing the field via an interface. +func (v *Issue) GetPrimaryName() string { return v.PrimaryName } + +// GetDescription returns Issue.Description, and is useful for accessing the field via an interface. +func (v *Issue) GetDescription() string { return v.Description } + +// GetType returns Issue.Type, and is useful for accessing the field via an interface. +func (v *Issue) GetType() IssueTypes { return v.Type } + +// IssueConnection includes the requested fields of the GraphQL type IssueConnection. +type IssueConnection struct { + TotalCount int `json:"totalCount"` + Edges []*IssueConnectionEdgesIssueEdge `json:"edges"` +} + +// GetTotalCount returns IssueConnection.TotalCount, and is useful for accessing the field via an interface. +func (v *IssueConnection) GetTotalCount() int { return v.TotalCount } + +// GetEdges returns IssueConnection.Edges, and is useful for accessing the field via an interface. +func (v *IssueConnection) GetEdges() []*IssueConnectionEdgesIssueEdge { return v.Edges } + +// IssueConnectionEdgesIssueEdge includes the requested fields of the GraphQL type IssueEdge. +type IssueConnectionEdgesIssueEdge struct { + Node *Issue `json:"node"` +} + +// GetNode returns IssueConnectionEdgesIssueEdge.Node, and is useful for accessing the field via an interface. +func (v *IssueConnectionEdgesIssueEdge) GetNode() *Issue { return v.Node } + +type IssueFilter struct { + AffectedService []string `json:"affectedService"` + PrimaryName []string `json:"primaryName"` + IssueMatchStatus []IssueMatchStatusValues `json:"issueMatchStatus"` + IssueType []IssueTypes `json:"issueType"` + ComponentVersionId []string `json:"componentVersionId"` + Search []string `json:"search"` +} + +// GetAffectedService returns IssueFilter.AffectedService, and is useful for accessing the field via an interface. +func (v *IssueFilter) GetAffectedService() []string { return v.AffectedService } + +// GetPrimaryName returns IssueFilter.PrimaryName, and is useful for accessing the field via an interface. +func (v *IssueFilter) GetPrimaryName() []string { return v.PrimaryName } + +// GetIssueMatchStatus returns IssueFilter.IssueMatchStatus, and is useful for accessing the field via an interface. +func (v *IssueFilter) GetIssueMatchStatus() []IssueMatchStatusValues { return v.IssueMatchStatus } + +// GetIssueType returns IssueFilter.IssueType, and is useful for accessing the field via an interface. +func (v *IssueFilter) GetIssueType() []IssueTypes { return v.IssueType } + +// GetComponentVersionId returns IssueFilter.ComponentVersionId, and is useful for accessing the field via an interface. +func (v *IssueFilter) GetComponentVersionId() []string { return v.ComponentVersionId } + +// GetSearch returns IssueFilter.Search, and is useful for accessing the field via an interface. +func (v *IssueFilter) GetSearch() []string { return v.Search } + +type IssueInput struct { + PrimaryName string `json:"primaryName"` + Description string `json:"description"` + Type IssueTypes `json:"type"` +} + +// GetPrimaryName returns IssueInput.PrimaryName, and is useful for accessing the field via an interface. +func (v *IssueInput) GetPrimaryName() string { return v.PrimaryName } + +// GetDescription returns IssueInput.Description, and is useful for accessing the field via an interface. +func (v *IssueInput) GetDescription() string { return v.Description } + +// GetType returns IssueInput.Type, and is useful for accessing the field via an interface. +func (v *IssueInput) GetType() IssueTypes { return v.Type } + +type IssueMatchStatusValues string + +const ( + IssueMatchStatusValuesNew IssueMatchStatusValues = "new" + IssueMatchStatusValuesRiskAccepted IssueMatchStatusValues = "risk_accepted" + IssueMatchStatusValuesFalsePositive IssueMatchStatusValues = "false_positive" + IssueMatchStatusValuesMitigated IssueMatchStatusValues = "mitigated" +) + +// IssueRepository includes the requested fields of the GraphQL type IssueRepository. +type IssueRepository struct { + Id string `json:"id"` + Name string `json:"name"` + Url string `json:"url"` +} + +// GetId returns IssueRepository.Id, and is useful for accessing the field via an interface. +func (v *IssueRepository) GetId() string { return v.Id } + +// GetName returns IssueRepository.Name, and is useful for accessing the field via an interface. +func (v *IssueRepository) GetName() string { return v.Name } + +// GetUrl returns IssueRepository.Url, and is useful for accessing the field via an interface. +func (v *IssueRepository) GetUrl() string { return v.Url } + +// IssueRepositoryConnection includes the requested fields of the GraphQL type IssueRepositoryConnection. +type IssueRepositoryConnection struct { + TotalCount int `json:"totalCount"` + Edges []*IssueRepositoryConnectionEdgesIssueRepositoryEdge `json:"edges"` +} + +// GetTotalCount returns IssueRepositoryConnection.TotalCount, and is useful for accessing the field via an interface. +func (v *IssueRepositoryConnection) GetTotalCount() int { return v.TotalCount } + +// GetEdges returns IssueRepositoryConnection.Edges, and is useful for accessing the field via an interface. +func (v *IssueRepositoryConnection) GetEdges() []*IssueRepositoryConnectionEdgesIssueRepositoryEdge { + return v.Edges +} + +// IssueRepositoryConnectionEdgesIssueRepositoryEdge includes the requested fields of the GraphQL type IssueRepositoryEdge. +type IssueRepositoryConnectionEdgesIssueRepositoryEdge struct { + Node *IssueRepository `json:"node"` +} + +// GetNode returns IssueRepositoryConnectionEdgesIssueRepositoryEdge.Node, and is useful for accessing the field via an interface. +func (v *IssueRepositoryConnectionEdgesIssueRepositoryEdge) GetNode() *IssueRepository { return v.Node } + +type IssueRepositoryFilter struct { + ServiceName []string `json:"serviceName"` + ServiceId []string `json:"serviceId"` + Name []string `json:"name"` +} + +// GetServiceName returns IssueRepositoryFilter.ServiceName, and is useful for accessing the field via an interface. +func (v *IssueRepositoryFilter) GetServiceName() []string { return v.ServiceName } + +// GetServiceId returns IssueRepositoryFilter.ServiceId, and is useful for accessing the field via an interface. +func (v *IssueRepositoryFilter) GetServiceId() []string { return v.ServiceId } + +// GetName returns IssueRepositoryFilter.Name, and is useful for accessing the field via an interface. +func (v *IssueRepositoryFilter) GetName() []string { return v.Name } + +type IssueRepositoryInput struct { + Name string `json:"name"` + Url string `json:"url"` +} + +// GetName returns IssueRepositoryInput.Name, and is useful for accessing the field via an interface. +func (v *IssueRepositoryInput) GetName() string { return v.Name } + +// GetUrl returns IssueRepositoryInput.Url, and is useful for accessing the field via an interface. +func (v *IssueRepositoryInput) GetUrl() string { return v.Url } + +type IssueTypes string + +const ( + IssueTypesVulnerability IssueTypes = "Vulnerability" + IssueTypesPolicyviolation IssueTypes = "PolicyViolation" + IssueTypesSecurityevent IssueTypes = "SecurityEvent" +) + +// IssueVariant includes the requested fields of the GraphQL type IssueVariant. +type IssueVariant struct { + Id string `json:"id"` + SecondaryName string `json:"secondaryName"` + IssueId string `json:"issueId"` +} + +// GetId returns IssueVariant.Id, and is useful for accessing the field via an interface. +func (v *IssueVariant) GetId() string { return v.Id } + +// GetSecondaryName returns IssueVariant.SecondaryName, and is useful for accessing the field via an interface. +func (v *IssueVariant) GetSecondaryName() string { return v.SecondaryName } + +// GetIssueId returns IssueVariant.IssueId, and is useful for accessing the field via an interface. +func (v *IssueVariant) GetIssueId() string { return v.IssueId } + +type IssueVariantInput struct { + SecondaryName string `json:"secondaryName"` + Description string `json:"description"` + IssueRepositoryId string `json:"issueRepositoryId"` + IssueId string `json:"issueId"` + Severity *SeverityInput `json:"severity,omitempty"` +} + +// GetSecondaryName returns IssueVariantInput.SecondaryName, and is useful for accessing the field via an interface. +func (v *IssueVariantInput) GetSecondaryName() string { return v.SecondaryName } + +// GetDescription returns IssueVariantInput.Description, and is useful for accessing the field via an interface. +func (v *IssueVariantInput) GetDescription() string { return v.Description } + +// GetIssueRepositoryId returns IssueVariantInput.IssueRepositoryId, and is useful for accessing the field via an interface. +func (v *IssueVariantInput) GetIssueRepositoryId() string { return v.IssueRepositoryId } + +// GetIssueId returns IssueVariantInput.IssueId, and is useful for accessing the field via an interface. +func (v *IssueVariantInput) GetIssueId() string { return v.IssueId } + +// GetSeverity returns IssueVariantInput.Severity, and is useful for accessing the field via an interface. +func (v *IssueVariantInput) GetSeverity() *SeverityInput { return v.Severity } + +type SeverityInput struct { + Vector string `json:"vector"` +} + +// GetVector returns SeverityInput.Vector, and is useful for accessing the field via an interface. +func (v *SeverityInput) GetVector() string { return v.Vector } + +// __CreateIssueInput is used internally by genqlient +type __CreateIssueInput struct { + Input *IssueInput `json:"input,omitempty"` +} + +// GetInput returns __CreateIssueInput.Input, and is useful for accessing the field via an interface. +func (v *__CreateIssueInput) GetInput() *IssueInput { return v.Input } + +// __CreateIssueRepositoryInput is used internally by genqlient +type __CreateIssueRepositoryInput struct { + Input *IssueRepositoryInput `json:"input,omitempty"` +} + +// GetInput returns __CreateIssueRepositoryInput.Input, and is useful for accessing the field via an interface. +func (v *__CreateIssueRepositoryInput) GetInput() *IssueRepositoryInput { return v.Input } + +// __CreateIssueVariantInput is used internally by genqlient +type __CreateIssueVariantInput struct { + Input *IssueVariantInput `json:"input,omitempty"` +} + +// GetInput returns __CreateIssueVariantInput.Input, and is useful for accessing the field via an interface. +func (v *__CreateIssueVariantInput) GetInput() *IssueVariantInput { return v.Input } + +// __GetIssueRepositoriesInput is used internally by genqlient +type __GetIssueRepositoriesInput struct { + Filter *IssueRepositoryFilter `json:"filter,omitempty"` +} + +// GetFilter returns __GetIssueRepositoriesInput.Filter, and is useful for accessing the field via an interface. +func (v *__GetIssueRepositoriesInput) GetFilter() *IssueRepositoryFilter { return v.Filter } + +// __GetIssuesInput is used internally by genqlient +type __GetIssuesInput struct { + Filter *IssueFilter `json:"filter,omitempty"` +} + +// GetFilter returns __GetIssuesInput.Filter, and is useful for accessing the field via an interface. +func (v *__GetIssuesInput) GetFilter() *IssueFilter { return v.Filter } + +// The query or mutation executed by CreateIssue. +const CreateIssue_Operation = ` +mutation CreateIssue ($input: IssueInput!) { + createIssue(input: $input) { + id + primaryName + description + type + } +} +` + +func CreateIssue( + ctx_ context.Context, + client_ graphql.Client, + input *IssueInput, +) (*CreateIssueResponse, error) { + req_ := &graphql.Request{ + OpName: "CreateIssue", + Query: CreateIssue_Operation, + Variables: &__CreateIssueInput{ + Input: input, + }, + } + var err_ error + + var data_ CreateIssueResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by CreateIssueRepository. +const CreateIssueRepository_Operation = ` +mutation CreateIssueRepository ($input: IssueRepositoryInput!) { + createIssueRepository(input: $input) { + id + name + url + } +} +` + +func CreateIssueRepository( + ctx_ context.Context, + client_ graphql.Client, + input *IssueRepositoryInput, +) (*CreateIssueRepositoryResponse, error) { + req_ := &graphql.Request{ + OpName: "CreateIssueRepository", + Query: CreateIssueRepository_Operation, + Variables: &__CreateIssueRepositoryInput{ + Input: input, + }, + } + var err_ error + + var data_ CreateIssueRepositoryResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by CreateIssueVariant. +const CreateIssueVariant_Operation = ` +mutation CreateIssueVariant ($input: IssueVariantInput!) { + createIssueVariant(input: $input) { + id + secondaryName + issueId + } +} +` + +func CreateIssueVariant( + ctx_ context.Context, + client_ graphql.Client, + input *IssueVariantInput, +) (*CreateIssueVariantResponse, error) { + req_ := &graphql.Request{ + OpName: "CreateIssueVariant", + Query: CreateIssueVariant_Operation, + Variables: &__CreateIssueVariantInput{ + Input: input, + }, + } + var err_ error + + var data_ CreateIssueVariantResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by GetIssueRepositories. +const GetIssueRepositories_Operation = ` +query GetIssueRepositories ($filter: IssueRepositoryFilter) { + IssueRepositories(filter: $filter) { + totalCount + edges { + node { + id + name + url + } + } + } +} +` + +func GetIssueRepositories( + ctx_ context.Context, + client_ graphql.Client, + filter *IssueRepositoryFilter, +) (*GetIssueRepositoriesResponse, error) { + req_ := &graphql.Request{ + OpName: "GetIssueRepositories", + Query: GetIssueRepositories_Operation, + Variables: &__GetIssueRepositoriesInput{ + Filter: filter, + }, + } + var err_ error + + var data_ GetIssueRepositoriesResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by GetIssues. +const GetIssues_Operation = ` +query GetIssues ($filter: IssueFilter) { + Issues(filter: $filter) { + totalCount + edges { + node { + id + primaryName + description + type + } + } + } +} +` + +func GetIssues( + ctx_ context.Context, + client_ graphql.Client, + filter *IssueFilter, +) (*GetIssuesResponse, error) { + req_ := &graphql.Request{ + OpName: "GetIssues", + Query: GetIssues_Operation, + Variables: &__GetIssuesInput{ + Filter: filter, + }, + } + var err_ error + + var data_ GetIssuesResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} diff --git a/scanner/nvd/client/genqlient.yaml b/scanner/nvd/client/genqlient.yaml new file mode 100644 index 00000000..20473b7f --- /dev/null +++ b/scanner/nvd/client/genqlient.yaml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +# SPDX-License-Identifier: Apache-2.0 + +# Default genqlient config; for full documentation see: +# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml +schema: ../../../internal/api/graphql/graph/schema/*.graphqls +operations: +- ./queries/*.graphql +generated: generated.go +package: client +use_struct_references: true diff --git a/scanner/nvd/client/queries/issue_create.graphql b/scanner/nvd/client/queries/issue_create.graphql new file mode 100644 index 00000000..65d77907 --- /dev/null +++ b/scanner/nvd/client/queries/issue_create.graphql @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +# SPDX-License-Identifier: Apache-2.0 + +mutation CreateIssue ($input: IssueInput!) { + # @genqlient(typename: Issue) + createIssue ( + input: $input + ) { + id + primaryName + description + type + } +} diff --git a/scanner/nvd/client/queries/issuerepository_create.graphql b/scanner/nvd/client/queries/issuerepository_create.graphql new file mode 100644 index 00000000..b101f822 --- /dev/null +++ b/scanner/nvd/client/queries/issuerepository_create.graphql @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +# SPDX-License-Identifier: Apache-2.0 + +mutation CreateIssueRepository ($input: IssueRepositoryInput!) { + # @genqlient(typename: IssueRepository) + createIssueRepository ( + input: $input + ) { + id + name + url + } +} diff --git a/scanner/nvd/client/queries/issuerepository_query.graphql b/scanner/nvd/client/queries/issuerepository_query.graphql new file mode 100644 index 00000000..81ba0a1b --- /dev/null +++ b/scanner/nvd/client/queries/issuerepository_query.graphql @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +# SPDX-License-Identifier: Apache-2.0 + +query GetIssueRepositories ($filter: IssueRepositoryFilter) { + # @genqlient(typename: IssueRepositoryConnection) + IssueRepositories ( + filter: $filter, + ) { + totalCount + edges { + # @genqlient(typename: IssueRepository) + node { + id + name + url + } + } + } +} diff --git a/scanner/nvd/client/queries/issues_query.graphql b/scanner/nvd/client/queries/issues_query.graphql new file mode 100644 index 00000000..3ac34db2 --- /dev/null +++ b/scanner/nvd/client/queries/issues_query.graphql @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +# SPDX-License-Identifier: Apache-2.0 + +query GetIssues ($filter: IssueFilter) { + # @genqlient(typename: IssueConnection) + Issues ( + filter: $filter, + ) { + totalCount + edges { + # @genqlient(typename: Issue) + node { + id + primaryName + description + type + } + } + } +} diff --git a/scanner/nvd/client/queries/issuevariant_create.graphql b/scanner/nvd/client/queries/issuevariant_create.graphql new file mode 100644 index 00000000..3b79da04 --- /dev/null +++ b/scanner/nvd/client/queries/issuevariant_create.graphql @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +# SPDX-License-Identifier: Apache-2.0 + +mutation CreateIssueVariant ($input: IssueVariantInput!) { + # @genqlient(typename: IssueVariant) + createIssueVariant ( + input: $input + ) { + id + secondaryName + issueId + } +} diff --git a/scanner/nvd/go.mod b/scanner/nvd/go.mod index 30b42c19..ca7320e1 100644 --- a/scanner/nvd/go.mod +++ b/scanner/nvd/go.mod @@ -1,3 +1,43 @@ -module github.com/cloudoperators/heureka/scanners/nvd +module github.wdf.sap.corp/cc/heureka/scanner/nvd -go 1.22.6 \ No newline at end of file +go 1.22.4 + +require ( + github.com/onsi/ginkgo/v2 v2.20.0 + github.com/onsi/gomega v1.34.1 + golang.org/x/time v0.5.0 +) + +require ( + github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/alexflint/go-arg v1.4.2 // indirect + github.com/alexflint/go-scalar v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/matryer/is v1.4.1 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/vektah/gqlparser/v2 v2.5.11 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/Khan/genqlient v0.7.0 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/machinebox/graphql v0.2.2 + github.com/sirupsen/logrus v1.9.3 +) diff --git a/scanner/nvd/go.sum b/scanner/nvd/go.sum new file mode 100644 index 00000000..808624fe --- /dev/null +++ b/scanner/nvd/go.sum @@ -0,0 +1,142 @@ +github.com/Khan/genqlient v0.7.0 h1:GZ1meyRnzcDTK48EjqB8t3bcfYvHArCUUvgOwpz1D4w= +github.com/Khan/genqlient v0.7.0/go.mod h1:HNyy3wZvuYwmW3Y7mkoQLZsa/R5n5yIRajS1kPBvSFM= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/alexflint/go-arg v1.4.2 h1:lDWZAXxpAnZUq4qwb86p/3rIJJ2Li81EoMbTMujhVa0= +github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM= +github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70= +github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo= +github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= +github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8= +github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/scanner/nvd/main.go b/scanner/nvd/main.go index 8761bd9b..ff0eae23 100644 --- a/scanner/nvd/main.go +++ b/scanner/nvd/main.go @@ -5,8 +5,82 @@ package main import ( "fmt" + "os" + "time" + + "github.com/kelseyhightower/envconfig" + log "github.com/sirupsen/logrus" + "github.wdf.sap.corp/cc/heureka/scanner/nvd/models" + "github.wdf.sap.corp/cc/heureka/scanner/nvd/processor" + "github.wdf.sap.corp/cc/heureka/scanner/nvd/scanner" ) +func init() { + // Log as JSON instead of the default ASCII formatter. + log.SetFormatter(&log.JSONFormatter{}) + + // Output to stdout instead of the default stderr + // Can be any io.Writer, see below for File example + log.SetOutput(os.Stdout) + + // Only log the warning severity or above. + log.SetLevel(log.DebugLevel) + + // Also report methods + log.SetReportCaller(true) +} + func main() { - fmt.Println("This is the nvd scanner") + var scannerCfg scanner.Config + err := envconfig.Process("heureka", &scannerCfg) + if err != nil { + log.WithFields(log.Fields{ + "errror": err, + }).Warn("Couldn't initialize scanner config") + } + scanner := scanner.NewScanner(scannerCfg) + + t := time.Now() + yearToday, monthToday, dayToday := time.Now().Date() + today := fmt.Sprintf("%d-%02d-%02dT23:59:59.000", yearToday, monthToday, dayToday) + yearYesterday, monthYesterday, dayYesterday := t.AddDate(0, 0, -1).Date() + yesterday := fmt.Sprintf("%d-%02d-%02dT00:00:00.000", yearYesterday, monthYesterday, dayYesterday) + + filter := models.CveFilter{ + PubStartDate: yesterday, + PubEndDate: today, + } + + cves, err := scanner.GetCVEs(filter) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + }).Error("Couldn't get CVEs") + } + + var processorCfg processor.Config + err = envconfig.Process("heureka", &processorCfg) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + }).Error("Couldn't configure new processor") + } + + processor := processor.NewProcessor(processorCfg) + err = processor.Setup() + if err != nil { + log.WithFields(log.Fields{ + "error": err, + }).Error("Couldn't setup new processor") + } + + for _, cve := range cves { + err = processor.Process(&cve.Cve) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + "CVEID": &cve.Cve.Id, + }).Warn("Couldn't process CVE") + } + } } diff --git a/scanner/nvd/models/models.go b/scanner/nvd/models/models.go new file mode 100644 index 00000000..619bc47d --- /dev/null +++ b/scanner/nvd/models/models.go @@ -0,0 +1,365 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +package models + +// Schemas +// CVE 2.0 +// https://csrc.nist.gov/schema/nvd/api/2.0/cve_api_json_2.0.schema +// CVS 3.1 +// https://csrc.nist.gov/schema/nvd/api/2.0/external/cvss-v3.1.json +// CVS 3.0 +// https://csrc.nist.gov/schema/nvd/api/2.0/external/cvss-v3.0.json +// CVS 2.0 +// https://csrc.nist.gov/schema/nvd/api/2.0/external/cvss-v2.0.json + +// Provide empty CVSS score for CVE which don't have +const emptyCVSS = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N" + +type CveResponse struct { + ResultsPerPage int `json:"resultsPerPage"` + StartIndex int `json:"startIndex"` + TotalResults int `json:"totalResults"` + Format string `json:"format"` + Version string `json:"version"` + Timestamp string `json:"timestamp"` + Vulnerabilities []CveItem `json:"vulnerabilities"` +} + +type CveItem struct { + Cve Cve `json:"cve"` +} + +type Cve struct { + Id string `json:"id,omitempty"` + SourceIdentifier string `json:"sourceIdentifier,omitempty"` + Published string `json:"published,omitempty"` + LastModified string `json:"lastModified,omitempty"` + VulnStatus string `json:"vulnStatus,omitempty"` + EvaluatorComment string `json:"evaluatorComment,omitempty"` + EvaluatorSolution string `json:"evaluatorSolution,omitempty"` + EvaluatorImpact string `json:"evaluatorImpact,omitempty"` + CISAExploitAdd string `json:"cisaExploitAdd,omitempty"` + CISAActionDue string `json:"cisaActionDue,omitempty"` + CveTags []CveTag `json:"cveTags"` + Descriptions []LangString `json:"descriptions"` + References []CveReference `json:"references"` + Metrics Metrics `json:"metrics,omitempty"` + Weaknesses []Weakness `json:"weaknesses,omitempty"` + Configurations []Configuration `json:"configurations,omitempty"` + VendorComments []VendorComment `json:"vendorComments,omitempty"` +} + +func (cve Cve) GetDescription(language string) string { + for _, description := range cve.Descriptions { + if description.Lang == language { + return description.Value + } + } + return "" +} + +// GetSeverityVector tries to fetch a CvssV31 vector string from the CVE +func (cve Cve) SeverityVector() string { + var vector string + // Try to first fetch Cvssv31 score + for _, metric := range cve.Metrics.CvssMetricV31 { + if len(metric.CvsssData.VectorString) > 0 { + vector = metric.CvsssData.VectorString + break + } else { + continue + } + } + + // If no vector has been found, set each base metric to "least impactful" + // or "neutral" value. Therefore we CVSS score cannot be really calculated + if len(vector) == 0 { + vector = emptyCVSS + } + return vector +} + +type CveTag struct { + SourceIdentifier string `json:"sourceIdentifier,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +type LangString struct { + Lang string `json:"lang"` + Value string `json:"value"` +} + +type CveReference struct { + Url string `json:"url"` + Source string `json:"source,omitempty"` + Tags []string `json:"tags.omitempty"` +} + +type Metrics struct { + CvssMetricV40 []CvssMetricV40 `json:"cvssMetricV40"` + CvssMetricV31 []CvssMetricV31 `json:"cvssMetricV31"` + CvssMetricV30 []CvssMetricV30 `json:"cvssMetricV30"` + CvssMetricV2 []CvssMetricV2 `json:"cvssMetricV2"` +} + +type CvssMetricV40 struct { + Source string `json:"source"` + Type string `json:"type"` + CvssData CvssV40 `json:"cvssData"` +} + +type CvssMetricV31 struct { + Source string `json:"source"` + Type string `json:"type"` + CvsssData CvssV31 `json:"cvssData"` + ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"` + ImpactScore float64 `json:"impactScore,omitempty"` +} + +type CvssMetricV30 struct { + Source string `json:"source"` + Type string `json:"type"` + CvssData CvssV30 `json:"cvssData"` + ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"` + ImpactScore float64 `json:"impactScore,omitempty"` +} + +type CvssMetricV2 struct { + Source string `json:"source"` + Type string `json:"type"` + CvssData CvssV20 `json:"cvssData"` + BaseSeverity string `json:"baseSeverity,omitempty"` + ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"` + ImpactScore float64 `json:"impactScore,omitempty"` + ACInsufInfo bool `json:"acInsufInfo,omitempty"` + ObtainAllPrivilege bool `json:"obtainAllPrivilege,omitempty"` + ObtainUserPrivilege bool `json:"obtainUserPrivilege,omitempty"` + ObtainOtherPrivilege bool `json:"obtainOtherPrivilege,omitempty"` + UserInteractionRequired bool `json:"userInteractionRequired,omitempty"` +} + +type CvssV20 struct { + Version string `json:"version"` + VectorString string `json:"vectorString"` + AccessVector string `json:"accessVector,omitempty"` + AccessComplexity string `json:"accessComplexity,omitempty"` + Authentication string `json:"authentication,omitempty"` + ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` + IntegrityImpact string `json:"integrityImpact,omitempty"` + AvailabilityImpact string `json:"availabilityImpact,omitempty"` + BaseScore float64 `json:"baseScore,omitempty"` + Exploitability string `json:"exploitability,omitempty"` + RemediationLevel string `json:"remediationLevel,omitempty"` + ReportConfidence string `json:"reportConfidence,omitempty"` + TemporalScore float64 `json:"temporalScore,omitempty"` + CollateralDamagePotential string `json:"collateralDamagePotential,omitempty"` + TargetDistribution string `json:"targetDistribution,omitempty"` + ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` + IntegrityRequirement string `json:"integrityRequirement,omitempty"` + AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` + EnvironmentalScore float64 `json:"environmentalScore,omitempty"` +} + +type CvssV30 struct { + Version string `json:"version"` + VectorString string `json:"vectorString"` + AttackVector string `json:"attackVector,omitempty"` + AttackComplexity string `json:"attackComplexity,omitempty"` + PrivilegesRequired string `json:"privilegesRequired,omitempty"` + UserInteraction string `json:"userInteraction,omitempty"` + Scope string `json:"scope,omitempty"` + ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` + IntegrityImpact string `json:"integrityImpact,omitempty"` + AvailabilityImpact string `json:"availabilityImpact,omitempty"` + BaseScore float64 `json:"baseScore"` + BaseSeverity string `json:"baseSeverity"` + ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"` + RemediationLevel string `json:"remediationLevel,omitempty"` + ReportConfidence string `json:"reportConfidence,omitempty"` + TemporalScore float64 `json:"temporalScore,omitempty"` + TemporalSeverity string `json:"temporalSeverity,omitempty"` + ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` + IntegrityRequirement string `json:"integrityRequirement,omitempty"` + AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` + ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"` + ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"` + ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"` + ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"` + ModifiedScope string `json:"modifiedScope,omitempty"` + ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"` + ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"` + ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"` + EnvironmentalScore float64 `json:"environmentalScore,omitempty"` + EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"` +} + +type CvssV31 struct { + Version string `json:"version"` + VectorString string `json:"vectorString"` + AttackVector string `json:"attackVector,omitempty"` + AttackComplexity string `json:"attackComplexity,omitempty"` + PrivilegesRequired string `json:"privilegesRequired,omitempty"` + UserInteraction string `json:"userInteraction,omitempty"` + Scope string `json:"scope,omitempty"` + ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` + IntegrityImpact string `json:"integrityImpact,omitempty"` + AvailabilityImpact string `json:"availabilityImpact,omitempty"` + BaseScore float64 `json:"baseScore"` + BaseSeverity string `json:"baseSeverity"` + ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"` + RemediationLevel string `json:"remediationLevel,omitempty"` + ReportConfidence string `json:"reportConfidence,omitempty"` + TemporalScore float64 `json:"temporalScore,omitempty"` + TemporalSeverity string `json:"temporalSeverity,omitempty"` + ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` + IntegrityRequirement string `json:"integrityRequirement,omitempty"` + AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` + ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"` + ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"` + ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"` + ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"` + ModifiedScope string `json:"modifiedScope,omitempty"` + ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"` + ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"` + ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"` + EnvironmentalScore float64 `json:"environmentalScore,omitempty"` + EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"` +} + +type CvssV40 struct { + Version string `json:"version"` + VectorString string `json:"vectorString"` + AttackVector string `json:"attackVector,omitempty"` + AttackComplexity string `json:"attackComplexity,omitempty"` + AttackRequirements string `json:"attackRequirements,omitempty"` + PrivilegesRequired string `json:"privilegesRequired,omitempty"` + UserInteraction string `json:"userInteraction,omitempty"` + VulnConfidentialityImpact string `json:"vulnConfidentialityImpact,omitempty"` + VulnIntegrityImpact string `json:"vulnIntegrityImpact,omitempty"` + VulnAvailabilityImpact string `json:"vulnAvailabilityImpact,omitempty"` + SubConfidentialityImpact string `json:"subConfidentialityImpact,omitempty"` + SubIntegrityImpact string `json:"subIntegrityImpact,omitempty"` + SubAvailabilityImpact string `json:"subAvailabilityImpact,omitempty"` + ExploitMaturity string `json:"exploitMaturity,omitempty"` + ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` + IntegrityRequirement string `json:"integrityRequirement,omitempty"` + AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` + ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"` + ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"` + ModifiedAttackRequirements string `json:"modifiedAttackRequirements,omitempty"` + ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"` + ModifiedUserInteracotion string `json:"modifiedUserInteraction,omitempty"` + ModifiedVulnConfidentialityImpact string `json:"modifiedVulnConfidentialityImpact,omitempty"` + ModifiedVulnIntegrityImpact string `json:"modifiedVulnIntegrityImpact,omitempty"` + ModifiedVulnAvailabilityImpact string `json:"modifiedVulnAvailabilityImpact,omitempty"` + ModifiedSubConfidentialityImpact string `json:"modifiedSubConfidentialityImpact,omitempty"` + ModifiedSubIntegrityImpact string `json:"modifiedSubIntegrityImpact,omitempty"` + ModifiedSubAvailabilityImpact string `json:"modifiedSubAvailabilityImpact,omitempty"` + Safety string `json:"safety,omitempty"` + Automatable string `json:"automatable,omitempty"` + Recovery string `json:"recovery,omitempty"` + ValueDensity string `json:"valueDensity,omitempty"` + VulnerabilityResponseEffort string `json:"vulnerabilityResponseEffort,omitempty"` + ProviderUrgency string `json:"providerUrgency,omitempty"` +} + +type Weakness struct { + Source string `json:"source"` + Type string `json:"type"` + Description []LangString `json:"description"` +} + +type Configurations struct { + Nodes []Node `json:"nodes"` +} + +type Node struct { + Operator string `json:"operator"` + Negate bool `json:"negate"` + CpeMatch []CpeMatch `json:"cpeMatch"` +} + +type CpeMatch struct { + Vulnerable bool `json:"vulnerable"` + Criteria string `json:"criteria"` + MatchCriteriaID string `json:"matchCriteriaId"` + VersionStartExcluding string `json:"versionStartExcluding,omitempty"` + VersionStartIncluding string `json:"versionStartIncluding,omitempty"` + VersionEndExcluding string `json:"versionEndExcluding,omitempty"` + VersionEndIncluding string `json:"versionEndIncluding,omitempty"` +} + +type Configuration struct { + Operator string `json:"operator,omitempty"` + Negate bool `json:"negate,omitempty"` + Nodes []Node `json:"nodes"` +} + +type VendorComment struct { + Organization string `json:"organization"` + Comment string `json:"comment"` + LastModified string `json:"lastModified"` +} + +type CveFilter struct { + // both pubStartDate and pubEndDate are required. The maximum allowable range when using any date range parameters is 120 consecutive days. + PubStartDate string + PubEndDate string + // both lastModStartDate and lastModEndDate are required. The maximum allowable range when using any date range parameters is 120 consecutive days. + ModStartDate string + ModEndDate string +} + +// +// Models for Issue +// +type Issue struct { + Id string `json:"id"` + Type string `json:"type"` + PrimaryName string `json:"primary_name"` + Description string `json:"description"` +} + +type IssueEdge struct { + Node *Issue `json:"node"` + Cursor *string `json:"cursor,omitempty"` +} + +type IssueConnection struct { + TotalCount int `json:"totalCount"` + Edges []*IssueEdge `json:"edges"` +} + +// +// Models for IssueVariant +// +type IssueVariant struct { + Id string `json:"id"` + SecondaryName string `json:"secondary_name"` + IssueId int64 `json:"issue_id"` +} + +// +// Models for IssueRepository +// +type IssueRepository struct { + Id string `json:"id"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + CreatedAt *string `json:"created_at,omitempty"` + UpdatedAt *string `json:"updated_at,omitempty"` +} + +type IssueRepositoryConnection struct { + TotalCount int `json:"totalCount"` + Edges []*IssueRepositoryEdge `json:"edges,omitempty"` +} + +type IssueRepositoryEdge struct { + Node *IssueRepository `json:"node"` + Cursor *string `json:"cursor,omitempty"` + Priority *int `json:"priority,omitempty"` + CreatedAt *string `json:"created_at,omitempty"` + UpdatedAt *string `json:"updated_at,omitempty"` +} diff --git a/scanner/nvd/processor/config.go b/scanner/nvd/processor/config.go new file mode 100644 index 00000000..e32e7072 --- /dev/null +++ b/scanner/nvd/processor/config.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +package processor + +type Config struct { + HeurekaUrl string `envconfig:"HEUREKA_URL" required:"true" json:"-"` + IssueRepositoryName string `envconfig:"ISSUE_REPOSITORY_NAME" required:"true" default:"nvd" json:"-"` + IssueRepositoryUrl string `envconfig:"ISSUE_REPOSITORY_URL" required:"true" default:"https://nvd.nist.gov/" json:"-"` +} diff --git a/scanner/nvd/processor/processor.go b/scanner/nvd/processor/processor.go new file mode 100644 index 00000000..e768cec5 --- /dev/null +++ b/scanner/nvd/processor/processor.go @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +package processor + +import ( + "context" + "fmt" + "net/http" + + "github.com/Khan/genqlient/graphql" + log "github.com/sirupsen/logrus" + "github.wdf.sap.corp/cc/heureka/scanner/nvd/client" + "github.wdf.sap.corp/cc/heureka/scanner/nvd/models" + "time" +) + +type Processor struct { + GraphqlClient graphql.Client + IssueRepositoryName string + IssueRepositoryId string + IssueRepositoryUrl string +} + +// NewProcessor +func NewProcessor(cfg Config) *Processor { + httpClient := http.Client{Timeout: time.Duration(10) * time.Second} + return &Processor{ + GraphqlClient: graphql.NewClient(cfg.HeurekaUrl, &httpClient), + IssueRepositoryName: cfg.IssueRepositoryName, + IssueRepositoryUrl: cfg.IssueRepositoryUrl, + } +} + +func (p *Processor) Setup() error { + // Check if there is already an IssueRepository with the same name + queryFilter := client.IssueRepositoryFilter{ + Name: []string{p.IssueRepositoryName}, + } + listRepositoriesResp, err := client.GetIssueRepositories(context.TODO(), p.GraphqlClient, &queryFilter) + + if (err == nil) && (listRepositoriesResp.IssueRepositories.TotalCount == 0) { + log.Warnf("There is no IssueRepository: %s", err) + + // Create new IssueRepository + issueRepositoryInput := client.IssueRepositoryInput{ + Name: p.IssueRepositoryName, + Url: p.IssueRepositoryUrl, + } + issueMutationResp, err := client.CreateIssueRepository(context.TODO(), p.GraphqlClient, &issueRepositoryInput) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + }).Error("Couldn't create new IssueRepository") + } + + // Save IssueRepositoryId + p.IssueRepositoryId = issueMutationResp.CreateIssueRepository.Id + log.WithFields(log.Fields{ + "issueRepositoryId": p.IssueRepositoryId, + }).Info("Created new IssueRepository") + } else { + + // Extract IssueRepositoryId + for _, ir := range listRepositoriesResp.IssueRepositories.Edges { + log.Debugf("nodeId: %s", ir.Node.Id) + p.IssueRepositoryId = ir.Node.Id + break + } + log.Debugf("IssueRepositoryId: %s", p.IssueRepositoryId) + } + return nil +} + +func (p *Processor) Process(cve *models.Cve) error { + var issueId string + + // Create new Issue + createIssueInput := client.IssueInput{ + PrimaryName: cve.Id, + Description: cve.GetDescription("en"), + Type: "Vulnerability", + } + issueMutationResp, err := client.CreateIssue(context.TODO(), p.GraphqlClient, &createIssueInput) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + }).Error("Couldn't create new Issue") + return fmt.Errorf("Couldn't create new Issue") + } + + issueId = issueMutationResp.CreateIssue.Id + log.WithFields(log.Fields{ + "issueID": issueId, + }).Info("Created new Issue") + + // Create new IssueVariant + issueVariantInput := client.IssueVariantInput{ + SecondaryName: cve.Id, + Description: cve.GetDescription("en"), + IssueRepositoryId: p.IssueRepositoryId, + IssueId: issueId, + Severity: &client.SeverityInput{ + Vector: cve.SeverityVector(), + }, + } + variantMutationResp, err := client.CreateIssueVariant(context.TODO(), p.GraphqlClient, &issueVariantInput) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + }).Error("Couldn't create new IssueVariant") + return fmt.Errorf("couldn't create new IssueVariant") + } + + log.WithFields(log.Fields{ + "issueVariantId": variantMutationResp.CreateIssueVariant.Id, + }).Info("Created new IssueVariant") + + return nil +} diff --git a/scanner/nvd/scanner/config.go b/scanner/nvd/scanner/config.go new file mode 100644 index 00000000..b3dca3a0 --- /dev/null +++ b/scanner/nvd/scanner/config.go @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +package scanner + +type Config struct { + NvdApiUrl string `envconfig:"NVD_API_URL" required:"true" json:"-"` + NvdApiKey string `envconfig:"NVD_API_KEY" required:"true" json:"-"` + // default value and maximum allowable limit is 2,000 + NvdResultsPerPage string `envconfig:"NVD_RESULTS_PER_PAGE" default:"2000" json:"-"` +} diff --git a/scanner/nvd/scanner/scanner.go b/scanner/nvd/scanner/scanner.go new file mode 100644 index 00000000..5dbbbffb --- /dev/null +++ b/scanner/nvd/scanner/scanner.go @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +package scanner + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + log "github.com/sirupsen/logrus" + "github.wdf.sap.corp/cc/heureka/scanner/nvd/models" + "golang.org/x/time/rate" +) + +type Scanner struct { + BaseURL string + ApiKey string + ResultsPerPage string + HTTPClient *http.Client + RateLimiter *rate.Limiter +} + +func (s *Scanner) Do(req *http.Request) (*http.Response, error) { + err := s.RateLimiter.Wait(context.Background()) + if err != nil { + return nil, err + } + resp, err := s.HTTPClient.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} + +func NewScanner(cfg Config) *Scanner { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + // The public rate limit (without an API key) is 5 requests in a rolling 30 second + // window; the rate limit with an API key is 50 requests in a rolling 30 second window + rl := rate.NewLimiter(rate.Every(30*time.Second/50), 50) + + return &Scanner{ + BaseURL: cfg.NvdApiUrl, + ApiKey: cfg.NvdApiKey, + ResultsPerPage: cfg.NvdResultsPerPage, + HTTPClient: &http.Client{Transport: tr}, + RateLimiter: rl, + } +} + +func (s *Scanner) GetCVEs(filter models.CveFilter) ([]models.CveItem, error) { + index := 0 + totalResults := 1 + + allCves := []models.CveItem{} + + for index < totalResults { + cveResponse := models.CveResponse{} + url := s.createUrl(filter, index) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("Couldn't create new request: %w", err) + } + + req.Header.Add("apiKey", s.ApiKey) + + resp, err := s.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("Couldn't send request: %w", err) + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("Couldn't read response body: %w", err) + } + + if err = json.Unmarshal(body, &cveResponse); err != nil { + return nil, fmt.Errorf("Couldn't unmarshall body into a CVE: %w", err) + } + + allCves = append(allCves, cveResponse.Vulnerabilities...) + index += cveResponse.ResultsPerPage + totalResults = cveResponse.TotalResults + } + + return allCves, nil +} + +func (s *Scanner) createUrl(filter models.CveFilter, startIndex int) string { + url := fmt.Sprintf("%s?resultsPerPage=%s&startIndex=%d", s.BaseURL, s.ResultsPerPage, startIndex) + if filter.PubStartDate != "" { + url += "&pubStartDate=" + filter.PubStartDate + } + if filter.PubEndDate != "" { + url += "&pubEndDate=" + filter.PubEndDate + } + if filter.ModStartDate != "" { + url += "&modStartDate=" + filter.ModStartDate + } + if filter.ModEndDate != "" { + url += "&modEndDate=" + filter.ModEndDate + } + + log.Debug("NVD URL: %s", url) + return url +} diff --git a/scanner/nvd/test/e2e/e2e_suite_test.go b/scanner/nvd/test/e2e/e2e_suite_test.go new file mode 100644 index 00000000..c4793f67 --- /dev/null +++ b/scanner/nvd/test/e2e/e2e_suite_test.go @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "testing" + + "github.wdf.sap.corp/cc/heureka/scanner/nvd/processor" +) + +var cveProcessor *processor.Processor +var cfg processor.Config + +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "e2e Suite") +} + +var _ = BeforeSuite(func() { + cfg = processor.Config{ + HeurekaUrl: "http://127.0.0.1:80/query", + IssueRepositoryName: "NVD", + IssueRepositoryUrl: "https://nvd.nist.gov", + } + + // Setup new processor + cveProcessor = processor.NewProcessor(cfg) + cveProcessor.IssueRepositoryName = cfg.IssueRepositoryName +}) diff --git a/scanner/nvd/test/e2e/processor_test.go b/scanner/nvd/test/e2e/processor_test.go new file mode 100644 index 00000000..d8ff73fc --- /dev/null +++ b/scanner/nvd/test/e2e/processor_test.go @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +// import ( +// "encoding/json" +// "fmt" + +// . "github.com/onsi/ginkgo/v2" +// . "github.com/onsi/gomega" +// "github.wdf.sap.corp/cc/heureka/scanner/nvd/models" +// ) + +// var _ = Describe("Submitting Issues", Ordered, Label("e2e", "Issues", "create"), func() { +// When("no issues exist", Label("CreateIssue"), func() { +// Context("and the CVE is valid", func() { +// It("creates a new Issue", func() { +// newCve := models.Cve{} +// cveJson := ` +// { +// "id": "CVE-2023-0001", +// "descriptions": [ +// { +// "lang": "en", +// "value": "Sample description of the vulnerability in English." +// }, +// { +// "lang": "es", +// "value": "Descripción de ejemplo de la vulnerabilidad en español." +// } +// ] +// } +// ` +// err := json.Unmarshal([]byte(cveJson), &newCve) +// Expect(err).To(BeNil()) + +// // Create new Issue +// _, err = cveProcessor.CreateIssue(&newCve) +// Expect(err).To(BeNil()) +// }) +// }) +// }) + +// When("issues exist", Label("GetIssueId"), func() { +// Context("and the CVE has no metrics", func() { +// It("returns a valid issue_id", Label("CVE:NoMetrics"), func() { +// cve := models.Cve{} +// cveJson := ` +// { +// "id": "CVE-2024-20083", +// "descriptions": [ +// { +// "lang": "en", +// "value": "Sample description of the vulnerability in English." +// }, +// { +// "lang": "es", +// "value": "Descripción de ejemplo de la vulnerabilidad en español." +// } +// ], +// "metrics": {} +// } +// ` +// err := json.Unmarshal([]byte(cveJson), &cve) +// Expect(err).To(BeNil()) + +// fmt.Printf("reponame: %s", cveProcessor.IssueRepositoryName) + +// // Get IssueId +// issueId, err := cveProcessor.GetIssueId(&cve) +// Expect(err).To(BeNil()) + +// // Get IssueRepositoryId +// issueRepositoryId, err := cveProcessor.GetIssueRepositoryId() +// Expect(err).To(BeNil()) + +// fmt.Printf("Severity: %s\n",cve.SeverityVector()) + +// // Create new IssueVariant +// issueVariantId, err := cveProcessor.CreateIssueVariant( +// issueId, +// issueRepositoryId, +// &cve, +// ) +// Expect(err).To(BeNil()) +// fmt.Printf("Issue ID: %s\tIssue Variant ID: %s\n", issueId, issueVariantId) +// }) +// }) +// }) +// })