mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 18:51:59 +00:00
chore: Remove sentinel code (#61814)
This feature has not shipped, and is currently not prioritized. Git history is always preserved, but less code to actively maintain on `main` is always good, so I think this change is a win. I am not the owner of this code, so I will defer to the graph team to decide if this should be merged or closed. Test plan: Code review to verify this change is ok, and CI to verify the code didn't break.
This commit is contained in:
parent
2b53ae6b49
commit
b255d93ecf
@ -205,7 +205,6 @@ go_library(
|
||||
"codeintel.graphql",
|
||||
"codeintel.policies.graphql",
|
||||
"codeintel.ranking.graphql",
|
||||
"codeintel.sentinel.graphql",
|
||||
"cody_context.graphql",
|
||||
"completions.graphql",
|
||||
"compute.graphql",
|
||||
|
||||
@ -1,350 +0,0 @@
|
||||
extend type Query {
|
||||
"""
|
||||
Return known vulnerabilities.
|
||||
"""
|
||||
vulnerabilities(
|
||||
"""
|
||||
The maximum number of results to return.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
If supplied, indicates which results to skip over during pagination.
|
||||
"""
|
||||
after: String
|
||||
): VulnerabilityConnection!
|
||||
|
||||
"""
|
||||
Return known vulnerability matches.
|
||||
"""
|
||||
vulnerabilityMatches(
|
||||
"""
|
||||
The maximum number of results to return.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
If supplied, indicates which results to skip over during pagination.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Programming language of the vulnerability.
|
||||
"""
|
||||
language: String
|
||||
|
||||
"""
|
||||
Severity of the vulnerability.
|
||||
"""
|
||||
severity: String
|
||||
|
||||
"""
|
||||
The name of the repository to filter by.
|
||||
"""
|
||||
repositoryName: String
|
||||
): VulnerabilityMatchConnection!
|
||||
|
||||
"""
|
||||
Return known vulnerability matches grouped by repository.
|
||||
"""
|
||||
vulnerabilityMatchesCountByRepository(
|
||||
"""
|
||||
The maximum number of results to return.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
If supplied, indicates which results to skip over during pagination.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
A string pattern that could match the name of a repository.
|
||||
"""
|
||||
repositoryName: String
|
||||
): VulnerabilityMatchCountByRepositoryConnection!
|
||||
|
||||
"""
|
||||
Returns a count of the vulnerability matches grouped by severity.
|
||||
"""
|
||||
vulnerabilityMatchesSummaryCounts: VulnerabilityMatchesSummaryCount!
|
||||
}
|
||||
|
||||
"""
|
||||
A page of vulnerabilities.
|
||||
"""
|
||||
type VulnerabilityConnection {
|
||||
"""
|
||||
The vulnerabilities on the page.
|
||||
"""
|
||||
nodes: [Vulnerability!]!
|
||||
|
||||
"""
|
||||
The total number of vulnerabilities across all pages.
|
||||
"""
|
||||
totalCount: Int
|
||||
|
||||
"""
|
||||
Information on how to fetch the next page.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
Vulnerabilities synced from the GitHub Advisory Database.
|
||||
"""
|
||||
type Vulnerability implements Node {
|
||||
"""
|
||||
The ID of the vulnerability.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
The CVE identifier of the vulnerability (e.g., CVE-2023-123).
|
||||
"""
|
||||
sourceID: String!
|
||||
|
||||
"""
|
||||
A short summary of the vulnerability.
|
||||
"""
|
||||
summary: String!
|
||||
|
||||
"""
|
||||
A longer description of the vulnerability.
|
||||
"""
|
||||
details: String!
|
||||
|
||||
"""
|
||||
Common Platform Enumeration identifiers related to this vulnerability.
|
||||
"""
|
||||
cpes: [String!]!
|
||||
|
||||
"""
|
||||
Common Weakness Enumeration identifiers related to this vulnerability.
|
||||
"""
|
||||
cwes: [String!]!
|
||||
|
||||
"""
|
||||
Other names this vulnerability is known by.
|
||||
"""
|
||||
aliases: [String!]!
|
||||
|
||||
"""
|
||||
Names of related vulnerabilities.
|
||||
"""
|
||||
related: [String!]!
|
||||
|
||||
"""
|
||||
The data source of this vulnerability.
|
||||
"""
|
||||
dataSource: String!
|
||||
|
||||
"""
|
||||
URLs indicating the source of this vulnerability.
|
||||
"""
|
||||
urls: [String!]!
|
||||
|
||||
"""
|
||||
A human-readable severity string.
|
||||
"""
|
||||
severity: String!
|
||||
|
||||
"""
|
||||
The Common Vulnerability Scoring System severity vector.
|
||||
"""
|
||||
cvssVector: String!
|
||||
|
||||
"""
|
||||
The Common Vulnerability Scoring System severity score.
|
||||
"""
|
||||
cvssScore: String!
|
||||
|
||||
"""
|
||||
The time this vulnerability was published.
|
||||
"""
|
||||
published: DateTime!
|
||||
|
||||
"""
|
||||
The last time this vulnerability was modified.
|
||||
"""
|
||||
modified: DateTime
|
||||
|
||||
"""
|
||||
The time this vulnerability was withdrawn.
|
||||
"""
|
||||
withdrawn: DateTime
|
||||
|
||||
"""
|
||||
A list of packages that are affected by this vulnerability.
|
||||
"""
|
||||
affectedPackages: [VulnerabilityAffectedPackage!]!
|
||||
}
|
||||
|
||||
"""
|
||||
A package affected by a vulnerability.
|
||||
"""
|
||||
type VulnerabilityAffectedPackage {
|
||||
"""
|
||||
The name of the package.
|
||||
"""
|
||||
packageName: String!
|
||||
|
||||
"""
|
||||
The language ecosystem.
|
||||
"""
|
||||
language: String!
|
||||
|
||||
"""
|
||||
A package namespace.
|
||||
"""
|
||||
namespace: String!
|
||||
|
||||
"""
|
||||
A list of constraints that identify affected versions of the package.
|
||||
"""
|
||||
versionConstraint: [String!]!
|
||||
|
||||
"""
|
||||
Whether or not there is a known fix.
|
||||
"""
|
||||
fixed: Boolean!
|
||||
|
||||
"""
|
||||
The version in which the fix was applied.
|
||||
"""
|
||||
fixedIn: String
|
||||
|
||||
"""
|
||||
A list of specific symbols affected by the vulnerability.
|
||||
"""
|
||||
affectedSymbols: [VulnerabilityAffectedSymbol!]!
|
||||
}
|
||||
|
||||
"""
|
||||
A specific symbol affected by a vulnerability.
|
||||
"""
|
||||
type VulnerabilityAffectedSymbol {
|
||||
"""
|
||||
A path to the document within the package source.
|
||||
"""
|
||||
path: String!
|
||||
|
||||
"""
|
||||
A list of symbols defined in that path.
|
||||
"""
|
||||
symbols: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
A page of vulnerability matches.
|
||||
"""
|
||||
type VulnerabilityMatchConnection {
|
||||
"""
|
||||
The vulnerability matches on the page.
|
||||
"""
|
||||
nodes: [VulnerabilityMatch!]!
|
||||
|
||||
"""
|
||||
The total number of vulnerability matches across all pages.
|
||||
"""
|
||||
totalCount: Int
|
||||
|
||||
"""
|
||||
Information on how to fetch the next page.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
A use of a known vulnerable-affected package.e
|
||||
"""
|
||||
type VulnerabilityMatch implements Node {
|
||||
"""
|
||||
The match ID.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
The vulnerability.
|
||||
"""
|
||||
vulnerability: Vulnerability!
|
||||
|
||||
"""
|
||||
The affected package that is used by the associated index.
|
||||
"""
|
||||
affectedPackage: VulnerabilityAffectedPackage!
|
||||
|
||||
"""
|
||||
The index record that contains a direct use of the affected package.
|
||||
"""
|
||||
preciseIndex: PreciseIndex!
|
||||
}
|
||||
|
||||
"""
|
||||
A count of the severities of vulnerability matches.
|
||||
"""
|
||||
type VulnerabilityMatchesSummaryCount {
|
||||
"""
|
||||
The number of matches with a severity of "CRITICAL".
|
||||
"""
|
||||
critical: Int!
|
||||
|
||||
"""
|
||||
The number of matches with a severity of "HIGH".
|
||||
"""
|
||||
high: Int!
|
||||
|
||||
"""
|
||||
The number of matches with a severity of "MEDIUM".
|
||||
"""
|
||||
medium: Int!
|
||||
|
||||
"""
|
||||
The number of matches with a severity of "LOW".
|
||||
"""
|
||||
low: Int!
|
||||
|
||||
"""
|
||||
The number of repos with a severity
|
||||
"""
|
||||
repository: Int!
|
||||
}
|
||||
|
||||
"""
|
||||
A page of vulnerability matches grouped by repository.
|
||||
"""
|
||||
type VulnerabilityMatchCountByRepositoryConnection {
|
||||
"""
|
||||
The vulnerability matches on the page.
|
||||
"""
|
||||
nodes: [VulnerabilityMatchCountByRepository!]!
|
||||
|
||||
"""
|
||||
The total number of vulnerability matches across all pages.
|
||||
"""
|
||||
totalCount: Int
|
||||
|
||||
"""
|
||||
Information on how to fetch the next page.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
Vulnerability matches count group by repository.
|
||||
"""
|
||||
type VulnerabilityMatchCountByRepository {
|
||||
"""
|
||||
The id of the grouping.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
The repository name.
|
||||
"""
|
||||
repositoryName: String!
|
||||
|
||||
"""
|
||||
The vulnerability matches count.
|
||||
"""
|
||||
matchCount: Int!
|
||||
}
|
||||
@ -226,16 +226,6 @@ func (r *NodeResolver) ToSite() (*siteResolver, bool) {
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (r *NodeResolver) ToVulnerability() (resolverstubs.VulnerabilityResolver, bool) {
|
||||
n, ok := r.Node.(resolverstubs.VulnerabilityResolver)
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (r *NodeResolver) ToVulnerabilityMatch() (resolverstubs.VulnerabilityMatchResolver, bool) {
|
||||
n, ok := r.Node.(resolverstubs.VulnerabilityMatchResolver)
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (r *NodeResolver) ToSiteConfigurationChange() (*SiteConfigurationChangeResolver, bool) {
|
||||
n, ok := r.Node.(*SiteConfigurationChangeResolver)
|
||||
return n, ok
|
||||
|
||||
@ -17,7 +17,6 @@ go_library(
|
||||
"//internal/codeintel/policies/transport/graphql",
|
||||
"//internal/codeintel/ranking/transport/graphql",
|
||||
"//internal/codeintel/resolvers",
|
||||
"//internal/codeintel/sentinel/transport/graphql",
|
||||
"//internal/codeintel/shared/lsifuploadstore",
|
||||
"//internal/codeintel/shared/resolvers",
|
||||
"//internal/codeintel/shared/resolvers/gitresolvers",
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
policiesgraphql "github.com/sourcegraph/sourcegraph/internal/codeintel/policies/transport/graphql"
|
||||
rankinggraphql "github.com/sourcegraph/sourcegraph/internal/codeintel/ranking/transport/graphql"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/resolvers"
|
||||
sentinelgraphql "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/transport/graphql"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/shared/lsifuploadstore"
|
||||
sharedresolvers "github.com/sourcegraph/sourcegraph/internal/codeintel/shared/resolvers"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/shared/resolvers/gitresolvers"
|
||||
@ -109,15 +108,6 @@ func Init(
|
||||
preciseIndexResolverFactory,
|
||||
)
|
||||
|
||||
sentinelRootResolver := sentinelgraphql.NewRootResolver(
|
||||
scopedContext("sentinel"),
|
||||
codeIntelServices.SentinelService,
|
||||
uploadLoaderFactory,
|
||||
indexLoaderFactory,
|
||||
locationResolverFactory,
|
||||
preciseIndexResolverFactory,
|
||||
)
|
||||
|
||||
rankingRootResolver := rankinggraphql.NewRootResolver(
|
||||
scopedContext("ranking"),
|
||||
codeIntelServices.RankingService,
|
||||
@ -129,7 +119,6 @@ func Init(
|
||||
codenavRootResolver,
|
||||
policyRootResolver,
|
||||
uploadRootResolver,
|
||||
sentinelRootResolver,
|
||||
rankingRootResolver,
|
||||
))
|
||||
enterpriseServices.NewCodeIntelUploadHandler = newUploadHandler
|
||||
|
||||
@ -11,7 +11,6 @@ go_library(
|
||||
"metrics_reporter.go",
|
||||
"policies.go",
|
||||
"ranking.go",
|
||||
"sentinel.go",
|
||||
"uploads_backfiller.go",
|
||||
"uploads_commitgraph.go",
|
||||
"uploads_expirer.go",
|
||||
@ -28,7 +27,6 @@ go_library(
|
||||
"//internal/codeintel/dependencies",
|
||||
"//internal/codeintel/policies",
|
||||
"//internal/codeintel/ranking",
|
||||
"//internal/codeintel/sentinel",
|
||||
"//internal/codeintel/shared/lsifuploadstore",
|
||||
"//internal/codeintel/uploads",
|
||||
"//internal/env",
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
package codeintel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/worker/job"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/worker/shared/init/codeintel"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel"
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type sentinelCVEScannerJob struct{}
|
||||
|
||||
func NewSentinelCVEScannerJob() job.Job {
|
||||
return &sentinelCVEScannerJob{}
|
||||
}
|
||||
|
||||
func (j *sentinelCVEScannerJob) Description() string {
|
||||
return "code-intel sentinel vulnerability scanner"
|
||||
}
|
||||
|
||||
func (j *sentinelCVEScannerJob) Config() []env.Config {
|
||||
return []env.Config{
|
||||
sentinel.DownloaderConfigInst,
|
||||
sentinel.MatcherConfigInst,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *sentinelCVEScannerJob) Routines(_ context.Context, observationCtx *observation.Context) ([]goroutine.BackgroundRoutine, error) {
|
||||
services, err := codeintel.InitServices(observationCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sentinel.CVEScannerJob(observationCtx, services.SentinelService), nil
|
||||
}
|
||||
@ -113,7 +113,6 @@ func LoadConfig(registerEnterpriseMigrators oobmigration.RegisterMigratorsFunc)
|
||||
"codeintel-upload-janitor": codeintel.NewUploadJanitorJob(),
|
||||
"codeintel-ranking-file-reference-counter": codeintel.NewRankingFileReferenceCounter(),
|
||||
"codeintel-uploadstore-expirer": codeintel.NewPreciseCodeIntelUploadExpirer(),
|
||||
"codeintel-sentinel-cve-scanner": codeintel.NewSentinelCVEScannerJob(),
|
||||
"codeintel-package-filter-applicator": codeintel.NewPackagesFilterApplicatorJob(),
|
||||
|
||||
"auth-sourcegraph-operator-cleaner": auth.NewSourcegraphOperatorCleaner(),
|
||||
|
||||
7
deps.bzl
7
deps.bzl
@ -4460,13 +4460,6 @@ def go_dependencies():
|
||||
sum = "h1:DiLBVp4DAcZlBVBEtJpNWZpZVq0AEeCY7Hqk8URVs4o=",
|
||||
version = "v0.1.1-0.20220428063043-89078438f170",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_pandatix_go_cvss",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/pandatix/go-cvss",
|
||||
sum = "h1:9441i+Sn/P/TP9kNBl3kI7mwYtNYFr1eN8JdsiybiMM=",
|
||||
version = "v0.5.2",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_pascaldekloe_goe",
|
||||
build_file_proto_mode = "disable_global",
|
||||
|
||||
5
go.mod
5
go.mod
@ -515,7 +515,7 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hexops/autogold/v2 v2.2.1
|
||||
github.com/hexops/gotextdiff v1.0.3
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
@ -552,7 +552,7 @@ require (
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@ -569,7 +569,6 @@ require (
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.22
|
||||
github.com/pandatix/go-cvss v0.5.2
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@ -1467,8 +1467,6 @@ github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.22 h1:0h+YoXSyipf6XQGyIaDg6z5jwRik1J
|
||||
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.22/go.mod h1:4OjcxgwdXzezqytxN534MooNmrxRD50geWZxTD7845s=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/pandatix/go-cvss v0.5.2 h1:9441i+Sn/P/TP9kNBl3kI7mwYtNYFr1eN8JdsiybiMM=
|
||||
github.com/pandatix/go-cvss v0.5.2/go.mod h1:u4HcNBqA9IY6PZHuH8Pac4VaGv5iAMyiXMex/FIfxcg=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
|
||||
@ -13,7 +13,6 @@ go_library(
|
||||
"//internal/codeintel/policies",
|
||||
"//internal/codeintel/ranking",
|
||||
"//internal/codeintel/reposcheduler",
|
||||
"//internal/codeintel/sentinel",
|
||||
"//internal/codeintel/shared",
|
||||
"//internal/codeintel/uploads",
|
||||
"//internal/database",
|
||||
|
||||
@ -9,7 +9,6 @@ go_library(
|
||||
"policies.go",
|
||||
"ranking.go",
|
||||
"root_resolver.go",
|
||||
"sentinel.go",
|
||||
"uploads.go",
|
||||
"utils.go",
|
||||
],
|
||||
|
||||
@ -12,7 +12,6 @@ type RootResolver interface {
|
||||
AutoindexingServiceResolver
|
||||
CodeNavServiceResolver
|
||||
PoliciesServiceResolver
|
||||
SentinelServiceResolver
|
||||
UploadsServiceResolver
|
||||
RankingServiceResolver
|
||||
}
|
||||
@ -22,7 +21,6 @@ type Resolver struct {
|
||||
codenavResolver CodeNavServiceResolver
|
||||
policiesRootResolver PoliciesServiceResolver
|
||||
uploadsRootResolver UploadsServiceResolver
|
||||
sentinelRootResolver SentinelServiceResolver
|
||||
rankingServiceResolver RankingServiceResolver
|
||||
}
|
||||
|
||||
@ -31,7 +29,6 @@ func NewCodeIntelResolver(
|
||||
codenavResolver CodeNavServiceResolver,
|
||||
policiesRootResolver PoliciesServiceResolver,
|
||||
uploadsRootResolver UploadsServiceResolver,
|
||||
sentinelRootResolver SentinelServiceResolver,
|
||||
rankingServiceResolver RankingServiceResolver,
|
||||
) *Resolver {
|
||||
return &Resolver{
|
||||
@ -39,7 +36,6 @@ func NewCodeIntelResolver(
|
||||
codenavResolver: codenavResolver,
|
||||
policiesRootResolver: policiesRootResolver,
|
||||
uploadsRootResolver: uploadsRootResolver,
|
||||
sentinelRootResolver: sentinelRootResolver,
|
||||
rankingServiceResolver: rankingServiceResolver,
|
||||
}
|
||||
}
|
||||
@ -65,12 +61,6 @@ func (r *Resolver) NodeResolvers() map[string]NodeByIDFunc {
|
||||
"PreciseIndex": func(ctx context.Context, id graphql.ID) (Node, error) {
|
||||
return r.uploadsRootResolver.PreciseIndexByID(ctx, id)
|
||||
},
|
||||
"Vulnerability": func(ctx context.Context, id graphql.ID) (Node, error) {
|
||||
return r.sentinelRootResolver.VulnerabilityByID(ctx, id)
|
||||
},
|
||||
"VulnerabilityMatch": func(ctx context.Context, id graphql.ID) (Node, error) {
|
||||
return r.sentinelRootResolver.VulnerabilityMatchByID(ctx, id)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,30 +79,6 @@ func unmarshalLegacyUploadID(id graphql.ID) (int64, error) {
|
||||
return strconv.ParseInt(rawID, 10, 64)
|
||||
}
|
||||
|
||||
func (r *Resolver) Vulnerabilities(ctx context.Context, args GetVulnerabilitiesArgs) (_ VulnerabilityConnectionResolver, err error) {
|
||||
return r.sentinelRootResolver.Vulnerabilities(ctx, args)
|
||||
}
|
||||
|
||||
func (r *Resolver) VulnerabilityMatches(ctx context.Context, args GetVulnerabilityMatchesArgs) (_ VulnerabilityMatchConnectionResolver, err error) {
|
||||
return r.sentinelRootResolver.VulnerabilityMatches(ctx, args)
|
||||
}
|
||||
|
||||
func (r *Resolver) VulnerabilityByID(ctx context.Context, id graphql.ID) (_ VulnerabilityResolver, err error) {
|
||||
return r.sentinelRootResolver.VulnerabilityByID(ctx, id)
|
||||
}
|
||||
|
||||
func (r *Resolver) VulnerabilityMatchByID(ctx context.Context, id graphql.ID) (_ VulnerabilityMatchResolver, err error) {
|
||||
return r.sentinelRootResolver.VulnerabilityMatchByID(ctx, id)
|
||||
}
|
||||
|
||||
func (r *Resolver) VulnerabilityMatchesSummaryCounts(ctx context.Context) (_ VulnerabilityMatchesSummaryCountResolver, err error) {
|
||||
return r.sentinelRootResolver.VulnerabilityMatchesSummaryCounts(ctx)
|
||||
}
|
||||
|
||||
func (r *Resolver) VulnerabilityMatchesCountByRepository(ctx context.Context, args GetVulnerabilityMatchCountByRepositoryArgs) (_ VulnerabilityMatchCountByRepositoryConnectionResolver, err error) {
|
||||
return r.sentinelRootResolver.VulnerabilityMatchesCountByRepository(ctx, args)
|
||||
}
|
||||
|
||||
func (r *Resolver) IndexerKeys(ctx context.Context, opts *IndexerKeyQueryArgs) (_ []string, err error) {
|
||||
return r.uploadsRootResolver.IndexerKeys(ctx, opts)
|
||||
}
|
||||
|
||||
@ -1,96 +0,0 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/graph-gophers/graphql-go"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/gqlutil"
|
||||
)
|
||||
|
||||
type SentinelServiceResolver interface {
|
||||
// Fetch vulnerabilities
|
||||
Vulnerabilities(ctx context.Context, args GetVulnerabilitiesArgs) (VulnerabilityConnectionResolver, error)
|
||||
VulnerabilityByID(ctx context.Context, id graphql.ID) (_ VulnerabilityResolver, err error)
|
||||
|
||||
// Fetch matches
|
||||
VulnerabilityMatches(ctx context.Context, args GetVulnerabilityMatchesArgs) (VulnerabilityMatchConnectionResolver, error)
|
||||
VulnerabilityMatchByID(ctx context.Context, id graphql.ID) (_ VulnerabilityMatchResolver, err error)
|
||||
VulnerabilityMatchesSummaryCounts(ctx context.Context) (VulnerabilityMatchesSummaryCountResolver, error)
|
||||
VulnerabilityMatchesCountByRepository(ctx context.Context, args GetVulnerabilityMatchCountByRepositoryArgs) (VulnerabilityMatchCountByRepositoryConnectionResolver, error)
|
||||
}
|
||||
|
||||
type (
|
||||
GetVulnerabilitiesArgs = PagedConnectionArgs
|
||||
VulnerabilityConnectionResolver = PagedConnectionWithTotalCountResolver[VulnerabilityResolver]
|
||||
VulnerabilityMatchConnectionResolver = PagedConnectionWithTotalCountResolver[VulnerabilityMatchResolver]
|
||||
VulnerabilityMatchCountByRepositoryConnectionResolver = PagedConnectionWithTotalCountResolver[VulnerabilityMatchCountByRepositoryResolver]
|
||||
)
|
||||
|
||||
type GetVulnerabilityMatchesArgs struct {
|
||||
PagedConnectionArgs
|
||||
Severity *string
|
||||
Language *string
|
||||
RepositoryName *string
|
||||
}
|
||||
|
||||
type VulnerabilityResolver interface {
|
||||
ID() graphql.ID
|
||||
SourceID() string
|
||||
Summary() string
|
||||
Details() string
|
||||
CPEs() []string
|
||||
CWEs() []string
|
||||
Aliases() []string
|
||||
Related() []string
|
||||
DataSource() string
|
||||
URLs() []string
|
||||
Severity() string
|
||||
CVSSVector() string
|
||||
CVSSScore() string
|
||||
Published() gqlutil.DateTime
|
||||
Modified() *gqlutil.DateTime
|
||||
Withdrawn() *gqlutil.DateTime
|
||||
AffectedPackages() []VulnerabilityAffectedPackageResolver
|
||||
}
|
||||
|
||||
type VulnerabilityAffectedPackageResolver interface {
|
||||
PackageName() string
|
||||
Language() string
|
||||
Namespace() string
|
||||
VersionConstraint() []string
|
||||
Fixed() bool
|
||||
FixedIn() *string
|
||||
AffectedSymbols() []VulnerabilityAffectedSymbolResolver
|
||||
}
|
||||
|
||||
type VulnerabilityAffectedSymbolResolver interface {
|
||||
Path() string
|
||||
Symbols() []string
|
||||
}
|
||||
|
||||
type VulnerabilityMatchResolver interface {
|
||||
ID() graphql.ID
|
||||
Vulnerability(ctx context.Context) (VulnerabilityResolver, error)
|
||||
AffectedPackage(ctx context.Context) (VulnerabilityAffectedPackageResolver, error)
|
||||
PreciseIndex(ctx context.Context) (PreciseIndexResolver, error)
|
||||
}
|
||||
|
||||
type VulnerabilityMatchesSummaryCountResolver interface {
|
||||
Critical() int32
|
||||
High() int32
|
||||
Medium() int32
|
||||
Low() int32
|
||||
Repository() int32
|
||||
}
|
||||
|
||||
type GetVulnerabilityMatchCountByRepositoryArgs struct {
|
||||
PagedConnectionArgs
|
||||
RepositoryName *string
|
||||
}
|
||||
|
||||
type VulnerabilityMatchCountByRepositoryResolver interface {
|
||||
ID() graphql.ID
|
||||
RepositoryName() string
|
||||
MatchCount() int32
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "sentinel",
|
||||
srcs = [
|
||||
"init.go",
|
||||
"observability.go",
|
||||
"service.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/codeintel/sentinel/internal/background",
|
||||
"//internal/codeintel/sentinel/internal/background/downloader",
|
||||
"//internal/codeintel/sentinel/internal/background/matcher",
|
||||
"//internal/codeintel/sentinel/internal/store",
|
||||
"//internal/codeintel/sentinel/shared",
|
||||
"//internal/database",
|
||||
"//internal/goroutine",
|
||||
"//internal/observation",
|
||||
],
|
||||
)
|
||||
@ -1,39 +0,0 @@
|
||||
package sentinel
|
||||
|
||||
import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background/downloader"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background/matcher"
|
||||
sentinelstore "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/store"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
func NewService(
|
||||
observationCtx *observation.Context,
|
||||
db database.DB,
|
||||
) *Service {
|
||||
return newService(
|
||||
scopedContext("service", observationCtx),
|
||||
sentinelstore.New(scopedContext("store", observationCtx), db),
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
DownloaderConfigInst = &downloader.Config{}
|
||||
MatcherConfigInst = &matcher.Config{}
|
||||
)
|
||||
|
||||
func CVEScannerJob(observationCtx *observation.Context, service *Service) []goroutine.BackgroundRoutine {
|
||||
return background.CVEScannerJob(
|
||||
scopedContext("cvescanner", observationCtx),
|
||||
service.store,
|
||||
DownloaderConfigInst,
|
||||
MatcherConfigInst,
|
||||
)
|
||||
}
|
||||
|
||||
func scopedContext(component string, parent *observation.Context) *observation.Context {
|
||||
return observation.ScopedContext("codeintel", "sentinel", component, parent)
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "background",
|
||||
srcs = ["init.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/codeintel/sentinel/internal/background/downloader",
|
||||
"//internal/codeintel/sentinel/internal/background/matcher",
|
||||
"//internal/codeintel/sentinel/internal/store",
|
||||
"//internal/goroutine",
|
||||
"//internal/observation",
|
||||
],
|
||||
)
|
||||
@ -1,37 +0,0 @@
|
||||
load("//dev:go_defs.bzl", "go_test")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "downloader",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"job.go",
|
||||
"metrics.go",
|
||||
"source_github.go",
|
||||
"source_govulndb.go",
|
||||
"source_osv.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background/downloader",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/actor",
|
||||
"//internal/codeintel/sentinel/internal/store",
|
||||
"//internal/codeintel/sentinel/shared",
|
||||
"//internal/env",
|
||||
"//internal/goroutine",
|
||||
"//internal/observation",
|
||||
"//lib/errors",
|
||||
"@com_github_mitchellh_mapstructure//:mapstructure",
|
||||
"@com_github_pandatix_go_cvss//20",
|
||||
"@com_github_pandatix_go_cvss//30",
|
||||
"@com_github_pandatix_go_cvss//31",
|
||||
"@com_github_prometheus_client_golang//prometheus",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "downloader_test",
|
||||
srcs = ["source_osv_test.go"],
|
||||
embed = [":downloader"],
|
||||
)
|
||||
@ -1,17 +0,0 @@
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
env.BaseConfig
|
||||
|
||||
DownloaderInterval time.Duration
|
||||
}
|
||||
|
||||
func (c *Config) Load() {
|
||||
c.DownloaderInterval = c.GetInterval("CODEINTEL_SENTINEL_DOWNLOADER_INTERVAL", "1h", "How frequently to sync the vulnerability database.")
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/store"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
func NewCVEDownloader(store store.Store, observationCtx *observation.Context, config *Config) goroutine.BackgroundRoutine {
|
||||
cveParser := &CVEParser{
|
||||
store: store,
|
||||
logger: log.Scoped("sentinel.parser"),
|
||||
}
|
||||
metrics := newMetrics(observationCtx)
|
||||
|
||||
return goroutine.NewPeriodicGoroutine(
|
||||
actor.WithInternalActor(context.Background()),
|
||||
goroutine.HandlerFunc(func(ctx context.Context) error {
|
||||
vulnerabilities, err := cveParser.handle(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
numVulnerabilitiesInserted, err := store.InsertVulnerabilities(ctx, vulnerabilities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metrics.numVulnerabilitiesInserted.Add(float64(numVulnerabilitiesInserted))
|
||||
return nil
|
||||
}),
|
||||
goroutine.WithName("codeintel.sentinel-cve-downloader"),
|
||||
goroutine.WithDescription("Periodically syncs GitHub advisory records into Postgres."),
|
||||
goroutine.WithInterval(config.DownloaderInterval),
|
||||
)
|
||||
}
|
||||
|
||||
type CVEParser struct {
|
||||
store store.Store
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func NewCVEParser() *CVEParser {
|
||||
return &CVEParser{
|
||||
logger: log.Scoped("sentinel.parser"),
|
||||
}
|
||||
}
|
||||
|
||||
func (parser *CVEParser) handle(ctx context.Context) ([]shared.Vulnerability, error) {
|
||||
return parser.ReadGitHubAdvisoryDB(ctx, false)
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type metrics struct {
|
||||
numVulnerabilitiesInserted prometheus.Counter
|
||||
}
|
||||
|
||||
func newMetrics(observationCtx *observation.Context) *metrics {
|
||||
counter := func(name, help string) prometheus.Counter {
|
||||
counter := prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: name,
|
||||
Help: help,
|
||||
})
|
||||
|
||||
observationCtx.Registerer.MustRegister(counter)
|
||||
return counter
|
||||
}
|
||||
|
||||
numVulnerabilitiesInserted := counter(
|
||||
"src_codeintel_sentinel_num_vulnerabilities_inserted_total",
|
||||
"The number of vulnerability records inserted into Postgres.",
|
||||
)
|
||||
|
||||
return &metrics{
|
||||
numVulnerabilitiesInserted: numVulnerabilitiesInserted,
|
||||
}
|
||||
}
|
||||
@ -1,190 +0,0 @@
|
||||
package downloader
|
||||
|
||||
// Fetch and parse vulnerabilities from the GitHub Security Advisories (GHSA) database.
|
||||
// GHSA uses the Open Source Vulnerability (OSV) format, with some custom extensions.
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
const advisoryDatabaseURL = "https://github.com/github/advisory-database/archive/refs/heads/main.zip"
|
||||
|
||||
// ReadGitHubAdvisoryDB fetches a copy of the GHSA database and converts it to the internal Vulnerability format
|
||||
func (parser *CVEParser) ReadGitHubAdvisoryDB(ctx context.Context, useLocalCache bool) (vulns []shared.Vulnerability, err error) {
|
||||
if useLocalCache {
|
||||
zipReader, err := os.Open("main.zip")
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to open zip file")
|
||||
}
|
||||
|
||||
return parser.ParseGitHubAdvisoryDB(zipReader)
|
||||
}
|
||||
|
||||
resp, err := http.Get(advisoryDatabaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.Newf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return parser.ParseGitHubAdvisoryDB(resp.Body)
|
||||
}
|
||||
|
||||
func (parser *CVEParser) ParseGitHubAdvisoryDB(ghsaReader io.Reader) (vulns []shared.Vulnerability, err error) {
|
||||
content, err := io.ReadAll(ghsaReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zr, err := zip.NewReader(bytes.NewReader(content), int64(len(content)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range zr.File {
|
||||
if filepath.Ext(f.Name) != ".json" {
|
||||
continue
|
||||
}
|
||||
|
||||
r, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var osvVuln OSV
|
||||
if err := json.NewDecoder(r).Decode(&osvVuln); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert OSV to Vulnerability using GHSA handler
|
||||
var g GHSA
|
||||
convertedVuln, err := parser.osvToVuln(osvVuln, g)
|
||||
if err != nil {
|
||||
if _, ok := err.(GHSAUnreviewedError); ok {
|
||||
continue
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
vulns = append(vulns, convertedVuln)
|
||||
}
|
||||
|
||||
return vulns, nil
|
||||
}
|
||||
|
||||
//
|
||||
// GHSA-specific structs and handlers
|
||||
//
|
||||
|
||||
type GHSADatabaseSpecific struct {
|
||||
Severity string `mapstructure:"severity" json:"severity"`
|
||||
GithubReviewed bool `mapstructure:"github_reviewed" json:"github_reviewed"`
|
||||
GithubReviewedAt time.Time `json:"github_reviewed_at"`
|
||||
GithubReviewedAtString string `mapstructure:"github_reviewed_at"`
|
||||
NvdPublishedAt time.Time `json:"nvd_published_at"`
|
||||
NvdPublishedAtString string `mapstructure:"nvd_published_at"`
|
||||
CweIDs []string `mapstructure:"cwe_ids" json:"cwe_ids"`
|
||||
}
|
||||
|
||||
type GHSA int64
|
||||
|
||||
func (g GHSA) topLevelHandler(o OSV, v *shared.Vulnerability) (err error) {
|
||||
var databaseSpecific GHSADatabaseSpecific
|
||||
if err := mapstructure.Decode(o.DatabaseSpecific, &databaseSpecific); err != nil {
|
||||
return errors.Wrap(err, "cannot map DatabaseSpecific to GHSADatabaseSpecific")
|
||||
}
|
||||
|
||||
// Only process reviewed GitHub vulnerabilities
|
||||
if !databaseSpecific.GithubReviewed {
|
||||
return GHSAUnreviewedError{"Vulnerability not reviewed"}
|
||||
}
|
||||
|
||||
// mapstructure won't parse times, so do it manually
|
||||
if databaseSpecific.NvdPublishedAtString != "" {
|
||||
databaseSpecific.NvdPublishedAt, err = time.Parse(time.RFC3339, databaseSpecific.NvdPublishedAtString)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse NvdPublishedAtString")
|
||||
}
|
||||
}
|
||||
if databaseSpecific.GithubReviewedAtString != "" {
|
||||
databaseSpecific.GithubReviewedAt, err = time.Parse(time.RFC3339, databaseSpecific.GithubReviewedAtString)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse GithubReviewedAtString")
|
||||
}
|
||||
}
|
||||
|
||||
v.DataSource = "https://github.com/advisories/" + o.ID
|
||||
v.Severity = databaseSpecific.Severity // Low, Medium, High, Critical // TODO: Override this with CVSS score if it exists
|
||||
v.CWEs = databaseSpecific.CweIDs
|
||||
|
||||
// Ideally use NVD publish date; fall back on GitHub review date
|
||||
v.PublishedAt = databaseSpecific.NvdPublishedAt
|
||||
if v.PublishedAt.IsZero() {
|
||||
v.PublishedAt = databaseSpecific.GithubReviewedAt
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g GHSA) affectedHandler(a OSVAffected, affectedPackage *shared.AffectedPackage) error {
|
||||
affectedPackage.Language = githubEcosystemToLanguage(a.Package.Ecosystem)
|
||||
affectedPackage.Namespace = "github:" + a.Package.Ecosystem
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GHSAUnreviewedError is used to indicate when a vulnerability has not been reviewed, and should be skipped
|
||||
type GHSAUnreviewedError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e GHSAUnreviewedError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func githubEcosystemToLanguage(ecosystem string) (language string) {
|
||||
switch ecosystem {
|
||||
case "Go":
|
||||
language = "go"
|
||||
case "Hex":
|
||||
language = "erlang"
|
||||
case "Maven":
|
||||
language = "java"
|
||||
case "NuGet":
|
||||
language = ".net"
|
||||
case "Packagist":
|
||||
language = "php"
|
||||
case "Pub":
|
||||
language = "dart"
|
||||
case "PyPI":
|
||||
language = "python"
|
||||
case "RubyGems":
|
||||
language = "ruby"
|
||||
case "crates.io":
|
||||
language = "rust"
|
||||
case "npm":
|
||||
language = "Javascript"
|
||||
default:
|
||||
language = ""
|
||||
}
|
||||
|
||||
return language
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
package downloader
|
||||
|
||||
// Parse vulnerabilities from the golang/VulnDB (Govulndb) database.
|
||||
// Govulndb uses the Open Source Vulnerability (OSV) format, with some custom extensions.
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
const govulndbAdvisoryDatabaseURL = "https://github.com/golang/vuln/archive/refs/heads/master.zip"
|
||||
|
||||
// ReadGoVulnDb fetches a copy of the Go Vulnerability Database and converts it to the internal Vulnerability format
|
||||
func (parser *CVEParser) ReadGoVulnDb(ctx context.Context, useLocalCache bool) (vulns []shared.Vulnerability, err error) {
|
||||
if useLocalCache {
|
||||
zipReader, err := os.Open("vulndb-govulndb.zip")
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to open zip file")
|
||||
}
|
||||
|
||||
return parser.ParseGovulndbAdvisoryDB(zipReader)
|
||||
}
|
||||
|
||||
resp, err := http.Get(govulndbAdvisoryDatabaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.Newf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return parser.ParseGitHubAdvisoryDB(resp.Body)
|
||||
}
|
||||
|
||||
func (parser *CVEParser) ParseGovulndbAdvisoryDB(govulndbReader io.Reader) (vulns []shared.Vulnerability, err error) {
|
||||
content, err := io.ReadAll(govulndbReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zr, err := zip.NewReader(bytes.NewReader(content), int64(len(content)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range zr.File {
|
||||
if filepath.Dir(f.Name) != "vulndb-master/data/osv" {
|
||||
continue
|
||||
}
|
||||
if filepath.Ext(f.Name) != ".json" {
|
||||
continue
|
||||
}
|
||||
|
||||
r, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var osvVuln OSV
|
||||
if err := json.NewDecoder(r).Decode(&osvVuln); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert OSV to Vulnerability using Govulndb handler
|
||||
var g Govulndb
|
||||
convertedVuln, err := parser.osvToVuln(osvVuln, g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vulns = append(vulns, convertedVuln)
|
||||
}
|
||||
|
||||
return vulns, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Govulndb-specific structs and handlers
|
||||
//
|
||||
|
||||
// GovulndbAffectedEcosystemSpecific represents the custom data format used by Govulndb for OSV.Affected.EcosystemSpecific
|
||||
type GovulndbAffectedEcosystemSpecific struct {
|
||||
Imports []struct {
|
||||
Path string `mapstructure:"path" json:"path"`
|
||||
Goos []string `mapstructure:"goos" json:"goos"`
|
||||
Symbols []string `mapstructure:"symbols" json:"symbols"`
|
||||
} `mapstructure:"imports" json:"imports"`
|
||||
}
|
||||
|
||||
// GovulndbAffectedDatabaseSpecific represents the custom data format used by Govulndb for OSV.Affected.DatabaseSpecific
|
||||
type GovulndbAffectedDatabaseSpecific struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Govulndb int64
|
||||
|
||||
func (g Govulndb) topLevelHandler(o OSV, v *shared.Vulnerability) error {
|
||||
v.DataSource = "https://pkg.go.dev/vuln/" + o.ID
|
||||
|
||||
// Govulndb doesn't provide any top-level database_specific data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g Govulndb) affectedHandler(a OSVAffected, affectedPackage *shared.AffectedPackage) error {
|
||||
affectedPackage.Namespace = "govulndb"
|
||||
|
||||
// Attempt to decode the JSON from an interface{} to GovulnDBAffectedEcosystemSpecific
|
||||
var es GovulndbAffectedEcosystemSpecific
|
||||
if err := mapstructure.Decode(a.EcosystemSpecific, &es); err != nil {
|
||||
return errors.Wrap(err, "cannot map DatabaseSpecific to GovulndbAffectedEcosystemSpecific")
|
||||
}
|
||||
|
||||
for _, i := range es.Imports {
|
||||
affectedPackage.AffectedSymbols = append(affectedPackage.AffectedSymbols, shared.AffectedSymbol{
|
||||
Path: i.Path,
|
||||
Symbols: i.Symbols,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,235 +0,0 @@
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
|
||||
gocvss20 "github.com/pandatix/go-cvss/20"
|
||||
gocvss30 "github.com/pandatix/go-cvss/30"
|
||||
gocvss31 "github.com/pandatix/go-cvss/31"
|
||||
)
|
||||
|
||||
// OSV represents the Open Source Vulnerability format.
|
||||
// See https://ossf.github.io/osv-schema/
|
||||
type OSV struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ID string `json:"id"`
|
||||
Modified time.Time `json:"modified"`
|
||||
Published time.Time `json:"published"`
|
||||
Withdrawn time.Time `json:"withdrawn"`
|
||||
Aliases []string `json:"aliases"`
|
||||
Related []string `json:"related"`
|
||||
Summary string `json:"summary"`
|
||||
Details string `json:"details"`
|
||||
Severity []struct {
|
||||
Type string `json:"type"`
|
||||
Score string `json:"score"`
|
||||
} `json:"severity"`
|
||||
Affected []OSVAffected `json:"affected"`
|
||||
References []struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
} `json:"references"`
|
||||
Credits []struct {
|
||||
Name string `json:"name"`
|
||||
Contact []string `json:"contact"`
|
||||
} `json:"credits"`
|
||||
DatabaseSpecific interface{} `json:"database_specific"` // Provider-specific data, parsed by topLevelHandler
|
||||
}
|
||||
|
||||
// OSVAffected describes packages which are affected by an OSV vulnerability
|
||||
type OSVAffected struct {
|
||||
Package struct {
|
||||
Ecosystem string `json:"ecosystem"`
|
||||
Name string `json:"name"`
|
||||
Purl string `json:"purl"`
|
||||
} `json:"package"`
|
||||
|
||||
Ranges []struct {
|
||||
Type string `json:"type"`
|
||||
Repo string `json:"repo"`
|
||||
Events []struct {
|
||||
Introduced string `json:"introduced"`
|
||||
Fixed string `json:"fixed"`
|
||||
LastAffected string `json:"last_affected"`
|
||||
Limit string `json:"limit"`
|
||||
} `json:"events"`
|
||||
DatabaseSpecific interface{} `json:"database_specific"`
|
||||
} `json:"ranges"`
|
||||
|
||||
Versions []string `json:"versions"`
|
||||
EcosystemSpecific interface{} `json:"ecosystem_specific"` // Provider-specific data, parsed by affectedHandler
|
||||
DatabaseSpecific interface{} `json:"database_specific"` // Provider-specific data, parsed by affectedHandler
|
||||
}
|
||||
|
||||
// DataSourceHandler allows vulnerability database to provide handlers for parsing database-specific data structures.
|
||||
// Custom data structures can be provided at various locations in OSV, and are named DatabaseSpecific or EcosystemSpecific.
|
||||
type DataSourceHandler interface {
|
||||
topLevelHandler(OSV, *shared.Vulnerability) error // Handle provider-specific data at the top level of the OSV struct
|
||||
affectedHandler(OSVAffected, *shared.AffectedPackage) error // Handle provider-specific data at the OSV.Affected level
|
||||
}
|
||||
|
||||
// osvToVuln converts an OSV-formatted vulnerability to Sourcegraph's internal Vulnerability format
|
||||
func (parser *CVEParser) osvToVuln(o OSV, dataSourceHandler DataSourceHandler) (vuln shared.Vulnerability, err error) {
|
||||
// Core sections:
|
||||
// - /General details
|
||||
// - Severity - TODO, need to loop over
|
||||
// - /Affected
|
||||
// - /References
|
||||
// - Credits
|
||||
// - /Database_specific
|
||||
|
||||
v := shared.Vulnerability{
|
||||
SourceID: o.ID,
|
||||
Summary: o.Summary,
|
||||
Details: o.Details,
|
||||
PublishedAt: o.Published,
|
||||
ModifiedAt: &o.Modified,
|
||||
WithdrawnAt: &o.Withdrawn,
|
||||
Related: o.Related,
|
||||
Aliases: o.Aliases,
|
||||
}
|
||||
|
||||
for _, reference := range o.References {
|
||||
v.URLs = append(v.URLs, reference.URL)
|
||||
}
|
||||
|
||||
// Parse custom data with a provider-specific handler
|
||||
if err := dataSourceHandler.topLevelHandler(o, &v); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
if len(o.Severity) > 1 {
|
||||
parser.logger.Warn(
|
||||
"unexpected number of severity values (>1)",
|
||||
log.String("type", "dataWarning"),
|
||||
log.String("sourceID", v.SourceID),
|
||||
log.String("actualCount", fmt.Sprint(len(o.Severity))),
|
||||
)
|
||||
}
|
||||
for _, severity := range o.Severity {
|
||||
v.CVSSVector = severity.Score
|
||||
|
||||
v.CVSSScore, v.Severity, err = parseCVSS(v.CVSSVector)
|
||||
if err != nil {
|
||||
parser.logger.Warn(
|
||||
"could not parse CVSS vector",
|
||||
log.String("type", "dataWarning"),
|
||||
log.String("sourceID", v.SourceID),
|
||||
log.String("cvssVector", v.CVSSVector),
|
||||
log.String("err", err.Error()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var pas []shared.AffectedPackage
|
||||
for _, affected := range o.Affected {
|
||||
var ap shared.AffectedPackage
|
||||
|
||||
ap.PackageName = affected.Package.Name
|
||||
ap.Language = affected.Package.Ecosystem
|
||||
|
||||
// Parse custom data with a provider-specific handler
|
||||
if err := dataSourceHandler.affectedHandler(affected, &ap); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
if len(affected.Ranges) > 1 {
|
||||
parser.logger.Warn(
|
||||
"unexpected number of affected.Ranges (>1)",
|
||||
log.String("type", "dataWarning"),
|
||||
log.String("sourceID", v.SourceID),
|
||||
log.String("actualNumRanges", fmt.Sprint(len(affected.Ranges))),
|
||||
)
|
||||
}
|
||||
|
||||
// In all observed cases a single range is used, so keep it simple
|
||||
for _, affectedRange := range affected.Ranges {
|
||||
// Implement dataSourceHandler.affectedRangeHandler here if needed
|
||||
|
||||
for _, event := range affectedRange.Events {
|
||||
if event.Introduced != "" {
|
||||
ap.VersionConstraint = append(ap.VersionConstraint, ">="+event.Introduced)
|
||||
}
|
||||
if event.Fixed != "" {
|
||||
ap.VersionConstraint = append(ap.VersionConstraint, "<"+event.Fixed)
|
||||
ap.Fixed = true
|
||||
fixed := event.Fixed
|
||||
ap.FixedIn = &fixed
|
||||
}
|
||||
if event.LastAffected != "" {
|
||||
ap.VersionConstraint = append(ap.VersionConstraint, "<="+event.LastAffected)
|
||||
}
|
||||
if event.Limit != "" {
|
||||
ap.VersionConstraint = append(ap.VersionConstraint, "<="+event.Limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(affected.Ranges) == 0 && len(affected.Versions) > 0 {
|
||||
// A version indicates a precise affected version, so it doesn't make sense to have >1
|
||||
if len(affected.Versions) > 1 {
|
||||
parser.logger.Warn(
|
||||
"unexpected number of affected versions (>1)",
|
||||
log.String("type", "dataWarning"),
|
||||
log.String("sourceID", v.SourceID),
|
||||
log.String("actual", v.CVSSVector),
|
||||
log.String("err", err.Error()),
|
||||
)
|
||||
}
|
||||
ap.VersionConstraint = append(ap.VersionConstraint, "="+affected.Versions[0])
|
||||
}
|
||||
|
||||
pas = append(pas, ap)
|
||||
}
|
||||
|
||||
v.AffectedPackages = pas
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseCVSS(cvssVector string) (score string, severity string, err error) {
|
||||
// Some data sources include trailing slashes
|
||||
cleanCvssVector := strings.TrimRight(cvssVector, "/")
|
||||
|
||||
var baseScore float64
|
||||
switch {
|
||||
case strings.HasPrefix(cvssVector, "CVSS:3.0"):
|
||||
cvss, err := gocvss30.ParseVector(cleanCvssVector)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
baseScore = cvss.BaseScore()
|
||||
|
||||
case strings.HasPrefix(cvssVector, "CVSS:3.1"):
|
||||
cvss, err := gocvss31.ParseVector(cleanCvssVector)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
baseScore = cvss.BaseScore()
|
||||
|
||||
// CVSS v2 does not have prefix, falls into this condition.
|
||||
default:
|
||||
cvss, err := gocvss20.ParseVector(cleanCvssVector)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
baseScore = cvss.BaseScore()
|
||||
}
|
||||
|
||||
// Implementation of rating is the same across all CVSS versions.
|
||||
// Notice CVSS v2.0 does not have a "rating" in its specification,
|
||||
// but has been used when CVSS v3 was published.
|
||||
severity, err = gocvss31.Rating(baseScore)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
score = fmt.Sprintf("%.1f", baseScore)
|
||||
return score, severity, nil
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package downloader
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_parseCVSS(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cvssVector string
|
||||
wantScore string
|
||||
wantSeverity string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid CVSS v2.0",
|
||||
cvssVector: "AV:L/AC:M/Au:S/C:P/I:P/A:P",
|
||||
wantScore: "4.1",
|
||||
wantSeverity: "MEDIUM",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid CVSS v3.0",
|
||||
cvssVector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
wantScore: "9.8",
|
||||
wantSeverity: "CRITICAL",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid CVSS v3.1",
|
||||
cvssVector: "CVSS:3.1/AV:A/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
wantScore: "4.3",
|
||||
wantSeverity: "MEDIUM",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid CVSS v3.1",
|
||||
cvssVector: "CVSS:3.1/AV:A/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
wantScore: "",
|
||||
wantSeverity: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotScore, gotSeverity, err := parseCVSS(tt.cvssVector)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseCVSS() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotScore != tt.wantScore {
|
||||
t.Errorf("parseCVSS() gotScore = %v, want %v", gotScore, tt.wantScore)
|
||||
}
|
||||
if gotSeverity != tt.wantSeverity {
|
||||
t.Errorf("parseCVSS() gotSeverity = %v, want %v", gotSeverity, tt.wantSeverity)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package background
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background/downloader"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background/matcher"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/store"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
func CVEScannerJob(
|
||||
observationCtx *observation.Context,
|
||||
store store.Store,
|
||||
downloaderConfig *downloader.Config,
|
||||
matcherConfig *matcher.Config,
|
||||
) []goroutine.BackgroundRoutine {
|
||||
if os.Getenv("RUN_EXPERIMENTAL_SENTINEL_JOBS") != "true" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []goroutine.BackgroundRoutine{
|
||||
downloader.NewCVEDownloader(store, observationCtx, downloaderConfig),
|
||||
matcher.NewCVEMatcher(store, observationCtx, matcherConfig),
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "matcher",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"job.go",
|
||||
"metrics.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/background/matcher",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/actor",
|
||||
"//internal/codeintel/sentinel/internal/store",
|
||||
"//internal/env",
|
||||
"//internal/goroutine",
|
||||
"//internal/observation",
|
||||
"@com_github_prometheus_client_golang//prometheus",
|
||||
],
|
||||
)
|
||||
@ -1,19 +0,0 @@
|
||||
package matcher
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
env.BaseConfig
|
||||
|
||||
MatcherInterval time.Duration
|
||||
BatchSize int
|
||||
}
|
||||
|
||||
func (c *Config) Load() {
|
||||
c.MatcherInterval = c.GetInterval("CODEINTEL_SENTINEL_MATCHER_INTERVAL", "1s", "How frequently to match existing records against known vulnerabilities.")
|
||||
c.BatchSize = c.GetInt("CODEINTEL_SENTINEL_BATCH_SIZE", "100", "How many precise indexes to scan at once for vulnerabilities.")
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package matcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/store"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
func NewCVEMatcher(store store.Store, observationCtx *observation.Context, config *Config) goroutine.BackgroundRoutine {
|
||||
metrics := newMetrics(observationCtx)
|
||||
|
||||
return goroutine.NewPeriodicGoroutine(
|
||||
actor.WithInternalActor(context.Background()),
|
||||
goroutine.HandlerFunc(func(ctx context.Context) error {
|
||||
numReferencesScanned, numVulnerabilityMatches, err := store.ScanMatches(ctx, config.BatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metrics.numReferencesScanned.Add(float64(numReferencesScanned))
|
||||
metrics.numVulnerabilityMatches.Add(float64(numVulnerabilityMatches))
|
||||
return nil
|
||||
}),
|
||||
goroutine.WithName("codeintel.sentinel-cve-matcher"),
|
||||
goroutine.WithDescription("Matches SCIP indexes against known vulnerabilities."),
|
||||
goroutine.WithInterval(config.MatcherInterval),
|
||||
)
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package matcher
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type metrics struct {
|
||||
numReferencesScanned prometheus.Counter
|
||||
numVulnerabilityMatches prometheus.Counter
|
||||
}
|
||||
|
||||
func newMetrics(observationCtx *observation.Context) *metrics {
|
||||
counter := func(name, help string) prometheus.Counter {
|
||||
counter := prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: name,
|
||||
Help: help,
|
||||
})
|
||||
|
||||
observationCtx.Registerer.MustRegister(counter)
|
||||
return counter
|
||||
}
|
||||
|
||||
numReferencesScanned := counter(
|
||||
"src_codeintel_sentinel_num_references_scanned_total",
|
||||
"The total number of references scanned for vulnerabilities.",
|
||||
)
|
||||
numVulnerabilityMatches := counter(
|
||||
"src_codeintel_sentinel_num_vulnerability_matches_total",
|
||||
"The total number of vulnerability matches found.",
|
||||
)
|
||||
|
||||
return &metrics{
|
||||
numReferencesScanned: numReferencesScanned,
|
||||
numVulnerabilityMatches: numVulnerabilityMatches,
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
load("//dev:go_defs.bzl", "go_test")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "store",
|
||||
srcs = [
|
||||
"matches.go",
|
||||
"observability.go",
|
||||
"store.go",
|
||||
"vulnerabilities.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/store",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/codeintel/sentinel/shared",
|
||||
"//internal/database",
|
||||
"//internal/database/basestore",
|
||||
"//internal/database/batch",
|
||||
"//internal/database/dbutil",
|
||||
"//internal/metrics",
|
||||
"//internal/observation",
|
||||
"@com_github_hashicorp_go_version//:go-version",
|
||||
"@com_github_keegancsmith_sqlf//:sqlf",
|
||||
"@com_github_lib_pq//:pq",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@io_opentelemetry_go_otel//attribute",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "store_test",
|
||||
timeout = "moderate",
|
||||
srcs = [
|
||||
"matches_test.go",
|
||||
"vulnerabilities_test.go",
|
||||
],
|
||||
embed = [":store"],
|
||||
tags = [
|
||||
# Test requires localhost database
|
||||
"requires-network",
|
||||
],
|
||||
deps = [
|
||||
"//internal/codeintel/sentinel/shared",
|
||||
"//internal/codeintel/uploads/shared",
|
||||
"//internal/database",
|
||||
"//internal/database/basestore",
|
||||
"//internal/database/dbtest",
|
||||
"//internal/observation",
|
||||
"@com_github_google_go_cmp//cmp",
|
||||
"@com_github_keegancsmith_sqlf//:sqlf",
|
||||
"@com_github_lib_pq//:pq",
|
||||
"@com_github_sourcegraph_log//logtest",
|
||||
],
|
||||
)
|
||||
@ -1,458 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/keegancsmith/sqlf"
|
||||
"github.com/lib/pq"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/basestore"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/batch"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/dbutil"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
func (s *store) VulnerabilityMatchByID(ctx context.Context, id int) (_ shared.VulnerabilityMatch, _ bool, err error) {
|
||||
ctx, _, endObservation := s.operations.vulnerabilityMatchByID.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("id", id),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
matches, _, err := scanVulnerabilityMatchesAndCount(s.db.Query(ctx, sqlf.Sprintf(vulnerabilityMatchByIDQuery, id)))
|
||||
if err != nil || len(matches) == 0 {
|
||||
return shared.VulnerabilityMatch{}, false, err
|
||||
}
|
||||
|
||||
return matches[0], true, nil
|
||||
}
|
||||
|
||||
const vulnerabilityMatchByIDQuery = `
|
||||
SELECT
|
||||
m.id,
|
||||
m.upload_id,
|
||||
vap.vulnerability_id,
|
||||
vap.package_name,
|
||||
vap.language,
|
||||
vap.namespace,
|
||||
vap.version_constraint,
|
||||
vap.fixed,
|
||||
vap.fixed_in,
|
||||
vas.path,
|
||||
vas.symbols,
|
||||
vul.severity,
|
||||
0 AS count
|
||||
FROM vulnerability_matches m
|
||||
LEFT JOIN vulnerability_affected_packages vap ON vap.id = m.vulnerability_affected_package_id
|
||||
LEFT JOIN vulnerability_affected_symbols vas ON vas.vulnerability_affected_package_id = vap.id
|
||||
LEFT JOIN vulnerabilities vul ON vap.vulnerability_id = vul.id
|
||||
WHERE m.id = %s
|
||||
`
|
||||
|
||||
func (s *store) GetVulnerabilityMatches(ctx context.Context, args shared.GetVulnerabilityMatchesArgs) (_ []shared.VulnerabilityMatch, _ int, err error) {
|
||||
ctx, _, endObservation := s.operations.getVulnerabilityMatches.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("limit", args.Limit),
|
||||
attribute.Int("offset", args.Offset),
|
||||
attribute.String("severity", args.Severity),
|
||||
attribute.String("language", args.Language),
|
||||
attribute.String("repositoryName", args.RepositoryName),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
var conds []*sqlf.Query
|
||||
if args.Language != "" {
|
||||
conds = append(conds, sqlf.Sprintf("vap.language = %s", args.Language))
|
||||
}
|
||||
if args.Severity != "" {
|
||||
conds = append(conds, sqlf.Sprintf("vul.severity = %s", args.Severity))
|
||||
}
|
||||
if args.RepositoryName != "" {
|
||||
conds = append(conds, sqlf.Sprintf("r.name = %s", args.RepositoryName))
|
||||
}
|
||||
if len(conds) == 0 {
|
||||
conds = append(conds, sqlf.Sprintf("TRUE"))
|
||||
}
|
||||
|
||||
return scanVulnerabilityMatchesAndCount(s.db.Query(ctx, sqlf.Sprintf(getVulnerabilityMatchesQuery, sqlf.Join(conds, " AND "), args.Limit, args.Offset)))
|
||||
}
|
||||
|
||||
const getVulnerabilityMatchesQuery = `
|
||||
WITH limited_matches AS (
|
||||
SELECT
|
||||
m.id,
|
||||
m.upload_id,
|
||||
m.vulnerability_affected_package_id
|
||||
FROM vulnerability_matches m
|
||||
ORDER BY id
|
||||
)
|
||||
SELECT
|
||||
m.id,
|
||||
m.upload_id,
|
||||
vap.vulnerability_id,
|
||||
vap.package_name,
|
||||
vap.language,
|
||||
vap.namespace,
|
||||
vap.version_constraint,
|
||||
vap.fixed,
|
||||
vap.fixed_in,
|
||||
vas.path,
|
||||
vas.symbols,
|
||||
vul.severity,
|
||||
COUNT(*) OVER() AS count
|
||||
FROM limited_matches m
|
||||
LEFT JOIN vulnerability_affected_packages vap ON vap.id = m.vulnerability_affected_package_id
|
||||
LEFT JOIN vulnerability_affected_symbols vas ON vas.vulnerability_affected_package_id = vap.id
|
||||
LEFT JOIN vulnerabilities vul ON vap.vulnerability_id = vul.id
|
||||
LEFT JOIN lsif_uploads lu ON m.upload_id = lu.id
|
||||
LEFT JOIN repo r ON r.id = lu.repository_id
|
||||
WHERE %s
|
||||
ORDER BY m.id, vap.id, vas.id
|
||||
LIMIT %s OFFSET %s
|
||||
`
|
||||
|
||||
func (s *store) GetVulnerabilityMatchesSummaryCount(ctx context.Context) (counts shared.GetVulnerabilityMatchesSummaryCounts, err error) {
|
||||
ctx, _, endObservation := s.operations.getVulnerabilityMatchesSummaryCount.With(ctx, &err, observation.Args{})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
row := s.db.QueryRow(ctx, sqlf.Sprintf(getVulnerabilityMatchesSummaryCounts))
|
||||
err = row.Scan(
|
||||
&counts.High,
|
||||
&counts.Medium,
|
||||
&counts.Low,
|
||||
&counts.Critical,
|
||||
&counts.Repositories,
|
||||
)
|
||||
if err != nil {
|
||||
return shared.GetVulnerabilityMatchesSummaryCounts{}, err
|
||||
}
|
||||
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
const getVulnerabilityMatchesSummaryCounts = `
|
||||
WITH limited_matches AS (
|
||||
SELECT
|
||||
m.id,
|
||||
m.upload_id,
|
||||
m.vulnerability_affected_package_id
|
||||
FROM vulnerability_matches m
|
||||
ORDER BY id
|
||||
)
|
||||
SELECT
|
||||
sum(case when vul.severity = 'HIGH' then 1 else 0 end) as high,
|
||||
sum(case when vul.severity = 'MEDIUM' then 1 else 0 end) as medium,
|
||||
sum(case when vul.severity = 'LOW' then 1 else 0 end) as low,
|
||||
sum(case when vul.severity = 'CRITICAL' then 1 else 0 end) as critical,
|
||||
count(distinct r.name) as repositories
|
||||
FROM limited_matches m
|
||||
LEFT JOIN vulnerability_affected_packages vap ON vap.id = m.vulnerability_affected_package_id
|
||||
LEFT JOIN vulnerability_affected_symbols vas ON vas.vulnerability_affected_package_id = vap.id
|
||||
LEFT JOIN vulnerabilities vul ON vap.vulnerability_id = vul.id
|
||||
LEFT JOIN lsif_uploads lu ON lu.id = m.upload_id
|
||||
LEFT JOIN repo r ON r.id = lu.repository_id
|
||||
`
|
||||
|
||||
func (s *store) GetVulnerabilityMatchesCountByRepository(ctx context.Context, args shared.GetVulnerabilityMatchesCountByRepositoryArgs) (_ []shared.VulnerabilityMatchesByRepository, _ int, err error) {
|
||||
ctx, _, endObservation := s.operations.getVulnerabilityMatchesCountByRepository.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("limit", args.Limit),
|
||||
attribute.Int("offset", args.Offset),
|
||||
attribute.String("repositoryName", args.RepositoryName),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
var conds []*sqlf.Query
|
||||
if args.RepositoryName != "" {
|
||||
conds = append(conds, sqlf.Sprintf("r.name ILIKE %s", "%"+args.RepositoryName+"%"))
|
||||
}
|
||||
if len(conds) == 0 {
|
||||
conds = append(conds, sqlf.Sprintf("TRUE"))
|
||||
}
|
||||
|
||||
rows, err := s.db.Query(ctx, sqlf.Sprintf(getVulnerabilityMatchesGroupedByRepos, sqlf.Join(conds, " AND "), args.Limit, args.Offset))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer func() { err = basestore.CloseRows(rows, err) }()
|
||||
|
||||
var matches []shared.VulnerabilityMatchesByRepository
|
||||
var totalCount int
|
||||
for rows.Next() {
|
||||
var match shared.VulnerabilityMatchesByRepository
|
||||
if err := rows.Scan(&match.ID, &match.RepositoryName, &match.MatchCount, &totalCount); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
matches = append(matches, match)
|
||||
}
|
||||
|
||||
return matches, totalCount, nil
|
||||
}
|
||||
|
||||
const getVulnerabilityMatchesGroupedByRepos = `
|
||||
select
|
||||
r.id,
|
||||
r.name,
|
||||
count(*) as count,
|
||||
COUNT(*) OVER() AS total_count
|
||||
from vulnerability_matches vm
|
||||
join lsif_uploads lu on lu.id = vm.upload_id
|
||||
join repo r on r.id = lu.repository_id
|
||||
where %s
|
||||
group by r.name, r.id
|
||||
order by count DESC
|
||||
limit %s offset %s
|
||||
`
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
func (s *store) ScanMatches(ctx context.Context, batchSize int) (numReferencesScanned int, numVulnerabilityMatches int, err error) {
|
||||
ctx, _, endObservation := s.operations.scanMatches.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("batchSize", batchSize),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
var a, b int
|
||||
err = s.db.WithTransact(ctx, func(tx *basestore.Store) error {
|
||||
type vulnerabilityMatch struct {
|
||||
UploadID int
|
||||
VulnerabilityAffectedPackageID int
|
||||
}
|
||||
numScanned := 0
|
||||
scanFilteredVulnerabilityMatches := basestore.NewFilteredSliceScanner(func(s dbutil.Scanner) (m vulnerabilityMatch, _ bool, _ error) {
|
||||
var (
|
||||
version string
|
||||
versionConstraints []string
|
||||
)
|
||||
|
||||
if err := s.Scan(&m.UploadID, &m.VulnerabilityAffectedPackageID, &version, pq.Array(&versionConstraints)); err != nil {
|
||||
return vulnerabilityMatch{}, false, err
|
||||
}
|
||||
|
||||
numScanned++
|
||||
matches, valid := versionMatchesConstraints(version, versionConstraints)
|
||||
_ = valid // TODO - log un-parseable versions
|
||||
|
||||
return m, matches, nil
|
||||
})
|
||||
|
||||
matches, err := scanFilteredVulnerabilityMatches(tx.Query(ctx, sqlf.Sprintf(
|
||||
scanMatchesQuery,
|
||||
batchSize,
|
||||
sqlf.Join(makeSchemeTtoVulnerabilityLanguageMappingConditions(), " OR "),
|
||||
)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Exec(ctx, sqlf.Sprintf(scanMatchesTemporaryTableQuery)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.WithInserter(
|
||||
ctx,
|
||||
tx.Handle(),
|
||||
"t_vulnerability_affected_packages",
|
||||
batch.MaxNumPostgresParameters,
|
||||
[]string{
|
||||
"upload_id",
|
||||
"vulnerability_affected_package_id",
|
||||
},
|
||||
func(inserter *batch.Inserter) error {
|
||||
for _, match := range matches {
|
||||
if err := inserter.Insert(
|
||||
ctx,
|
||||
match.UploadID,
|
||||
match.VulnerabilityAffectedPackageID,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
numMatched, _, err := basestore.ScanFirstInt(tx.Query(ctx, sqlf.Sprintf(scanMatchesUpdateQuery)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a = numScanned
|
||||
b = numMatched
|
||||
return nil
|
||||
})
|
||||
|
||||
return a, b, err
|
||||
}
|
||||
|
||||
const scanMatchesQuery = `
|
||||
WITH
|
||||
candidates AS (
|
||||
SELECT u.id
|
||||
FROM lsif_uploads u
|
||||
JOIN repo r ON r.id = u.repository_id
|
||||
WHERE
|
||||
u.state = 'completed' AND
|
||||
r.deleted_at IS NULL AND
|
||||
r.blocked IS NULL AND
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM lsif_uploads_vulnerability_scan uvs
|
||||
WHERE
|
||||
uvs.upload_id = u.id AND
|
||||
-- TODO: we'd rather compare this against vuln update times
|
||||
uvs.last_scanned_at < NOW()
|
||||
)
|
||||
ORDER BY u.id
|
||||
LIMIT %s
|
||||
),
|
||||
locked_candidates AS (
|
||||
INSERT INTO lsif_uploads_vulnerability_scan (upload_id, last_scanned_at)
|
||||
SELECT id, NOW() FROM candidates
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING upload_id
|
||||
)
|
||||
SELECT
|
||||
r.dump_id,
|
||||
vap.id,
|
||||
r.version,
|
||||
vap.version_constraint
|
||||
FROM locked_candidates lc
|
||||
JOIN lsif_references r ON r.dump_id = lc.upload_id
|
||||
JOIN vulnerability_affected_packages vap ON
|
||||
-- NOTE: This is currently a bit of a hack that works to find some
|
||||
-- good matches with the dataset we have. We should have a better
|
||||
-- way to match on a normalized name here, or have rules per types
|
||||
-- of language ecosystem.
|
||||
r.name LIKE '%%' || vap.package_name || '%%'
|
||||
WHERE %s
|
||||
`
|
||||
|
||||
const scanMatchesTemporaryTableQuery = `
|
||||
CREATE TEMPORARY TABLE t_vulnerability_affected_packages (
|
||||
upload_id INT NOT NULL,
|
||||
vulnerability_affected_package_id INT NOT NULL
|
||||
) ON COMMIT DROP
|
||||
`
|
||||
|
||||
const scanMatchesUpdateQuery = `
|
||||
WITH ins AS (
|
||||
INSERT INTO vulnerability_matches (upload_id, vulnerability_affected_package_id)
|
||||
SELECT upload_id, vulnerability_affected_package_id FROM t_vulnerability_affected_packages
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT COUNT(*) FROM ins
|
||||
`
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
var scanVulnerabilityMatchesAndCount = func(rows basestore.Rows, queryErr error) ([]shared.VulnerabilityMatch, int, error) {
|
||||
matches, totalCount, err := basestore.NewSliceWithCountScanner(func(s dbutil.Scanner) (match shared.VulnerabilityMatch, count int, _ error) {
|
||||
var (
|
||||
vap shared.AffectedPackage
|
||||
vas shared.AffectedSymbol
|
||||
vul shared.Vulnerability
|
||||
fixedIn string
|
||||
)
|
||||
|
||||
if err := s.Scan(
|
||||
&match.ID,
|
||||
&match.UploadID,
|
||||
&match.VulnerabilityID,
|
||||
// RHS(s) of left join (may be null)
|
||||
&dbutil.NullString{S: &vap.PackageName},
|
||||
&dbutil.NullString{S: &vap.Language},
|
||||
&dbutil.NullString{S: &vap.Namespace},
|
||||
pq.Array(&vap.VersionConstraint),
|
||||
&dbutil.NullBool{B: &vap.Fixed},
|
||||
&dbutil.NullString{S: &fixedIn},
|
||||
&dbutil.NullString{S: &vas.Path},
|
||||
pq.Array(vas.Symbols),
|
||||
&dbutil.NullString{S: &vul.Severity},
|
||||
&count,
|
||||
); err != nil {
|
||||
return shared.VulnerabilityMatch{}, 0, err
|
||||
}
|
||||
|
||||
if fixedIn != "" {
|
||||
vap.FixedIn = &fixedIn
|
||||
}
|
||||
if vas.Path != "" {
|
||||
vap.AffectedSymbols = append(vap.AffectedSymbols, vas)
|
||||
}
|
||||
if vap.PackageName != "" {
|
||||
match.AffectedPackage = vap
|
||||
}
|
||||
|
||||
return match, count, nil
|
||||
})(rows, queryErr)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return flattenMatches(matches), totalCount, nil
|
||||
}
|
||||
|
||||
var flattenMatches = func(ms []shared.VulnerabilityMatch) []shared.VulnerabilityMatch {
|
||||
flattened := []shared.VulnerabilityMatch{}
|
||||
for _, m := range ms {
|
||||
i := len(flattened) - 1
|
||||
if len(flattened) == 0 || flattened[i].ID != m.ID {
|
||||
flattened = append(flattened, m)
|
||||
} else {
|
||||
if flattened[i].AffectedPackage.PackageName == "" {
|
||||
flattened[i].AffectedPackage = m.AffectedPackage
|
||||
} else {
|
||||
symbols := flattened[i].AffectedPackage.AffectedSymbols
|
||||
symbols = append(symbols, m.AffectedPackage.AffectedSymbols...)
|
||||
flattened[i].AffectedPackage.AffectedSymbols = symbols
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flattened
|
||||
}
|
||||
|
||||
func versionMatchesConstraints(versionString string, constraints []string) (matches, valid bool) {
|
||||
v, err := version.NewVersion(versionString)
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
constraint, err := version.NewConstraint(strings.Join(constraints, ","))
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
return constraint.Check(v), true
|
||||
}
|
||||
|
||||
var scipSchemeToVulnerabilityLanguage = map[string]string{
|
||||
"gomod": "go",
|
||||
"npm": "Javascript",
|
||||
// TODO - java mapping
|
||||
}
|
||||
|
||||
func makeSchemeTtoVulnerabilityLanguageMappingConditions() []*sqlf.Query {
|
||||
schemes := make([]string, 0, len(scipSchemeToVulnerabilityLanguage))
|
||||
for scheme := range scipSchemeToVulnerabilityLanguage {
|
||||
schemes = append(schemes, scheme)
|
||||
}
|
||||
sort.Strings(schemes)
|
||||
|
||||
mappings := make([]*sqlf.Query, 0, len(schemes))
|
||||
for _, scheme := range schemes {
|
||||
mappings = append(mappings, sqlf.Sprintf("(r.scheme = %s AND vap.language = %s)", scheme, scipSchemeToVulnerabilityLanguage[scheme]))
|
||||
}
|
||||
|
||||
return mappings
|
||||
}
|
||||
@ -1,536 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/keegancsmith/sqlf"
|
||||
"github.com/lib/pq"
|
||||
"github.com/sourcegraph/log/logtest"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
uploadsshared "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/basestore"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/dbtest"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
func TestVulnerabilityMatchByID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(t))
|
||||
store := New(observation.TestContextTB(t), db)
|
||||
|
||||
setupReferences(t, db)
|
||||
|
||||
if _, err := store.InsertVulnerabilities(ctx, testVulnerabilities); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if _, _, err := store.ScanMatches(ctx, 100); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
match, ok, err := store.VulnerabilityMatchByID(ctx, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting vulnerability match: %s", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected match to exist")
|
||||
}
|
||||
|
||||
expectedMatch := shared.VulnerabilityMatch{
|
||||
ID: 3,
|
||||
UploadID: 52,
|
||||
VulnerabilityID: 1,
|
||||
AffectedPackage: badConfig,
|
||||
}
|
||||
if diff := cmp.Diff(expectedMatch, match); diff != "" {
|
||||
t.Errorf("unexpected vulnerability match (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVulnerabilityMatches(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(t))
|
||||
store := New(observation.TestContextTB(t), db)
|
||||
|
||||
/*
|
||||
* Setup references is inserting seven (7) total references.
|
||||
* Five (5) of them are vulnerable versions
|
||||
* (three (3) for go-nacelle/config and two (2) for go-mockgen/xtools)
|
||||
* the remaining two (2) of the references is of the fixed version.
|
||||
*/
|
||||
setupReferences(t, db)
|
||||
highVulnerabilityCount := 3
|
||||
mediumVulnerabilityCount := 2
|
||||
totalVulnerableVersionsInserted := highVulnerabilityCount + mediumVulnerabilityCount // 5
|
||||
|
||||
highAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-nacelle/config",
|
||||
VersionConstraint: []string{"<= v1.2.5"},
|
||||
}
|
||||
mediumAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-mockgen/xtools",
|
||||
VersionConstraint: []string{"<= v1.3.5"},
|
||||
}
|
||||
|
||||
mockVulnerabilities := []shared.Vulnerability{
|
||||
{ID: 1, SourceID: "CVE-ABC", Severity: "HIGH", AffectedPackages: []shared.AffectedPackage{highAffectedPackage}},
|
||||
{ID: 2, SourceID: "CVE-DEF", Severity: "HIGH"},
|
||||
{ID: 3, SourceID: "CVE-GHI", Severity: "HIGH"},
|
||||
{ID: 4, SourceID: "CVE-JKL", Severity: "MEDIUM", AffectedPackages: []shared.AffectedPackage{mediumAffectedPackage}},
|
||||
{ID: 5, SourceID: "CVE-MNO", Severity: "MEDIUM"},
|
||||
{ID: 6, SourceID: "CVE-PQR", Severity: "MEDIUM"},
|
||||
{ID: 7, SourceID: "CVE-STU", Severity: "LOW"},
|
||||
{ID: 8, SourceID: "CVE-VWX", Severity: "LOW"},
|
||||
{ID: 9, SourceID: "CVE-Y&Z", Severity: "CRITICAL"},
|
||||
}
|
||||
|
||||
if _, err := store.InsertVulnerabilities(ctx, mockVulnerabilities); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if _, _, err := store.ScanMatches(ctx, 1000); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
/*
|
||||
* Test
|
||||
*/
|
||||
|
||||
args := shared.GetVulnerabilityMatchesArgs{Limit: 10, Offset: 0}
|
||||
matches, totalCount, err := store.GetVulnerabilityMatches(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if len(matches) != totalVulnerableVersionsInserted {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", len(matches), totalCount)
|
||||
}
|
||||
|
||||
/*
|
||||
* Test Severity filter
|
||||
*/
|
||||
|
||||
t.Run("Test severity filter", func(t *testing.T) {
|
||||
args.Severity = "HIGH"
|
||||
high, totalCount, err := store.GetVulnerabilityMatches(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if len(high) != highVulnerabilityCount {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", 3, totalCount)
|
||||
}
|
||||
|
||||
args.Severity = "MEDIUM"
|
||||
medium, totalCount, err := store.GetVulnerabilityMatches(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if len(medium) != mediumVulnerabilityCount {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", 2, totalCount)
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Test Language filter
|
||||
*/
|
||||
|
||||
t.Run("Test language filter", func(t *testing.T) {
|
||||
args = shared.GetVulnerabilityMatchesArgs{Limit: 10, Offset: 0, Language: "go", Severity: ""}
|
||||
goMatches, totalCount, err := store.GetVulnerabilityMatches(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if len(goMatches) != totalVulnerableVersionsInserted {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", 2, totalCount)
|
||||
}
|
||||
|
||||
args = shared.GetVulnerabilityMatchesArgs{Limit: 10, Offset: 0, Language: "typescript", Severity: ""}
|
||||
typescriptMatches, totalCount, err := store.GetVulnerabilityMatches(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if len(typescriptMatches) != 0 {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", 2, totalCount)
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Test Repository filter
|
||||
*/
|
||||
|
||||
t.Run("Test repository filter", func(t *testing.T) {
|
||||
args = shared.GetVulnerabilityMatchesArgs{Limit: 10, Offset: 0, RepositoryName: "github.com/go-nacelle/config"}
|
||||
nacelleMatches, totalCount, err := store.GetVulnerabilityMatches(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if len(nacelleMatches) != highVulnerabilityCount {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", 2, totalCount)
|
||||
}
|
||||
|
||||
args = shared.GetVulnerabilityMatchesArgs{Limit: 10, Offset: 0, RepositoryName: "github.com/go-mockgen/xtools"}
|
||||
xToolsMatches, totalCount, err := store.GetVulnerabilityMatches(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if len(xToolsMatches) != mediumVulnerabilityCount {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", 2, totalCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetVulberabilityMatchesCountByRepository(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(t))
|
||||
store := New(observation.TestContextTB(t), db)
|
||||
|
||||
/*
|
||||
* Setup references is inserting seven (7) total references.
|
||||
* Five (5) of them are vulnerable versions
|
||||
* (three (3) for go-nacelle/config and two (2) for go-mockgen/xtools)
|
||||
* the remaining two (2) of the references is of the fixed version.
|
||||
*/
|
||||
setupReferences(t, db)
|
||||
var highVulnerabilityCount int32 = 3
|
||||
var mediumVulnerabilityCount int32 = 2
|
||||
|
||||
highAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-nacelle/config",
|
||||
VersionConstraint: []string{"<= v1.2.5"},
|
||||
}
|
||||
mediumAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-mockgen/xtools",
|
||||
VersionConstraint: []string{"<= v1.3.5"},
|
||||
}
|
||||
mockVulnerabilities := []shared.Vulnerability{
|
||||
{ID: 1, SourceID: "CVE-ABC", Severity: "HIGH", AffectedPackages: []shared.AffectedPackage{highAffectedPackage}},
|
||||
{ID: 2, SourceID: "CVE-DEF", Severity: "HIGH"},
|
||||
{ID: 3, SourceID: "CVE-GHI", Severity: "HIGH"},
|
||||
{ID: 4, SourceID: "CVE-JKL", Severity: "MEDIUM", AffectedPackages: []shared.AffectedPackage{mediumAffectedPackage}},
|
||||
{ID: 5, SourceID: "CVE-MNO", Severity: "MEDIUM"},
|
||||
{ID: 6, SourceID: "CVE-PQR", Severity: "MEDIUM"},
|
||||
}
|
||||
|
||||
if _, err := store.InsertVulnerabilities(ctx, mockVulnerabilities); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if _, _, err := store.ScanMatches(ctx, 1000); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
// Test
|
||||
args := shared.GetVulnerabilityMatchesCountByRepositoryArgs{Limit: 10}
|
||||
grouping, totalCount, err := store.GetVulnerabilityMatchesCountByRepository(ctx, args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting vulnerability matches: %s", err)
|
||||
}
|
||||
|
||||
expectedMatches := []shared.VulnerabilityMatchesByRepository{
|
||||
{
|
||||
ID: 2,
|
||||
RepositoryName: "github.com/go-nacelle/config",
|
||||
MatchCount: highVulnerabilityCount,
|
||||
},
|
||||
{
|
||||
ID: 75,
|
||||
RepositoryName: "github.com/go-mockgen/xtools",
|
||||
MatchCount: mediumVulnerabilityCount,
|
||||
},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expectedMatches, grouping); diff != "" {
|
||||
t.Errorf("unexpected vulnerability matches (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
if totalCount != len(expectedMatches) {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", len(expectedMatches), totalCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVulnerabilityMatchesSummaryCount(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(t))
|
||||
store := New(observation.TestContextTB(t), db)
|
||||
handle := basestore.NewWithHandle(db.Handle())
|
||||
|
||||
/* Insert uploads for four (4) repositories */
|
||||
insertUploads(t, db,
|
||||
uploadsshared.Upload{ID: 50, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 51, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 52, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 53, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 54, RepositoryID: 75, RepositoryName: "github.com/go-mockgen/xtools"},
|
||||
uploadsshared.Upload{ID: 55, RepositoryID: 75, RepositoryName: "github.com/go-mockgen/xtools"},
|
||||
uploadsshared.Upload{ID: 56, RepositoryID: 75, RepositoryName: "github.com/go-mockgen/xtools"},
|
||||
uploadsshared.Upload{ID: 57, RepositoryID: 90, RepositoryName: "github.com/testify/config"},
|
||||
uploadsshared.Upload{ID: 58, RepositoryID: 90, RepositoryName: "github.com/testify/config"},
|
||||
uploadsshared.Upload{ID: 59, RepositoryID: 90, RepositoryName: "github.com/testify/config"},
|
||||
uploadsshared.Upload{ID: 60, RepositoryID: 90, RepositoryName: "github.com/testify/config"},
|
||||
uploadsshared.Upload{ID: 61, RepositoryID: 90, RepositoryName: "github.com/testify/config"},
|
||||
uploadsshared.Upload{ID: 62, RepositoryID: 200, RepositoryName: "github.com/go-sentinel/config"},
|
||||
uploadsshared.Upload{ID: 63, RepositoryID: 200, RepositoryName: "github.com/go-sentinel/config"},
|
||||
)
|
||||
|
||||
/*
|
||||
* Insert ten (10) total vulnerable package reference.
|
||||
* - Three (3) are high severity
|
||||
* - Two (2) are medium severity
|
||||
* - Four (4) are critical severity
|
||||
* - Low (1) is low severity
|
||||
*/
|
||||
if err := handle.Exec(context.Background(), sqlf.Sprintf(`
|
||||
INSERT INTO lsif_references (scheme, name, version, dump_id)
|
||||
VALUES
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.3', 50), -- high vulnerability
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.4', 51), -- high vulnerability
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.5', 52), -- high vulnerability
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.6', 53),
|
||||
('gomod', 'github.com/go-mockgen/xtools', 'v1.3.2', 54), -- medium vulnerability
|
||||
('gomod', 'github.com/go-mockgen/xtools', 'v1.3.3', 55), -- medium vulnerability
|
||||
('gomod', 'github.com/go-mockgen/xtools', 'v1.3.6', 56),
|
||||
('gomod', 'github.com/testify/config', 'v1.0.1', 57), -- critical vulnerability
|
||||
('gomod', 'github.com/testify/config', 'v1.0.2', 58), -- critical vulnerability
|
||||
('gomod', 'github.com/testify/config', 'v1.0.3', 59), -- critical vulnerability
|
||||
('gomod', 'github.com/testify/config', 'v1.0.5', 60), -- critical vulnerability
|
||||
('gomod', 'github.com/testify/config', 'v1.0.6', 61),
|
||||
('gomod', 'github.com/go-sentinel/config', 'v2.3.0', 62), -- low vulnerability
|
||||
('gomod', 'github.com/go-sentinel/config', 'v2.3.6', 63)
|
||||
`)); err != nil {
|
||||
t.Fatalf("failed to insert references: %s", err)
|
||||
}
|
||||
|
||||
var critical int32 = 4
|
||||
var high int32 = 3
|
||||
var medium int32 = 2
|
||||
var low int32 = 1
|
||||
var totalRepos int32 = 4
|
||||
|
||||
criticalAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "testify/config",
|
||||
VersionConstraint: []string{"<= v1.0.5"},
|
||||
}
|
||||
highAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-nacelle/config",
|
||||
VersionConstraint: []string{"<= v1.2.5"},
|
||||
}
|
||||
mediumAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-mockgen/xtools",
|
||||
VersionConstraint: []string{"<= v1.3.5"},
|
||||
}
|
||||
lowAffectedPackage := shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-sentinel/config",
|
||||
VersionConstraint: []string{"<= v2.3.5"},
|
||||
}
|
||||
mockVulnerabilities := []shared.Vulnerability{
|
||||
{ID: 1, SourceID: "CVE-ABC", Severity: "HIGH", AffectedPackages: []shared.AffectedPackage{highAffectedPackage}},
|
||||
{ID: 2, SourceID: "CVE-DEF", Severity: "HIGH"},
|
||||
{ID: 3, SourceID: "CVE-GHI", Severity: "HIGH"},
|
||||
{ID: 4, SourceID: "CVE-JKL", Severity: "MEDIUM", AffectedPackages: []shared.AffectedPackage{mediumAffectedPackage}},
|
||||
{ID: 5, SourceID: "CVE-MNO", Severity: "MEDIUM"},
|
||||
{ID: 6, SourceID: "CVE-PQR", Severity: "MEDIUM"},
|
||||
{ID: 7, SourceID: "CVE-STU", Severity: "LOW", AffectedPackages: []shared.AffectedPackage{lowAffectedPackage}},
|
||||
{ID: 8, SourceID: "CVE-VWX", Severity: "LOW"},
|
||||
{ID: 9, SourceID: "CVE-Y&Z", Severity: "CRITICAL", AffectedPackages: []shared.AffectedPackage{criticalAffectedPackage}},
|
||||
}
|
||||
|
||||
if _, err := store.InsertVulnerabilities(ctx, mockVulnerabilities); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
if _, _, err := store.ScanMatches(ctx, 1000); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
/*
|
||||
* Test
|
||||
*/
|
||||
|
||||
summaryCount, err := store.GetVulnerabilityMatchesSummaryCount(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting vulnerability matches summary counts: %s", err)
|
||||
}
|
||||
|
||||
expectedSummaryCount := shared.GetVulnerabilityMatchesSummaryCounts{
|
||||
Critical: critical,
|
||||
High: high,
|
||||
Medium: medium,
|
||||
Low: low,
|
||||
Repositories: totalRepos,
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expectedSummaryCount, summaryCount); diff != "" {
|
||||
t.Errorf("unexpected vulnerability matches summary counts (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func setupReferences(t *testing.T, db database.DB) {
|
||||
store := basestore.NewWithHandle(db.Handle())
|
||||
|
||||
insertUploads(t, db,
|
||||
uploadsshared.Upload{ID: 50, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 51, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 52, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 53, RepositoryID: 2, RepositoryName: "github.com/go-nacelle/config"},
|
||||
uploadsshared.Upload{ID: 54, RepositoryID: 75, RepositoryName: "github.com/go-mockgen/xtools"},
|
||||
uploadsshared.Upload{ID: 55, RepositoryID: 75, RepositoryName: "github.com/go-mockgen/xtools"},
|
||||
uploadsshared.Upload{ID: 56, RepositoryID: 75, RepositoryName: "github.com/go-mockgen/xtools"},
|
||||
)
|
||||
|
||||
if err := store.Exec(context.Background(), sqlf.Sprintf(`
|
||||
-- Insert five (5) total vulnerable package reference.
|
||||
INSERT INTO lsif_references (scheme, name, version, dump_id)
|
||||
VALUES
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.3', 50), -- vulnerability
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.4', 51), -- vulnerability
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.5', 52), -- vulnerability
|
||||
('gomod', 'github.com/go-nacelle/config', 'v1.2.6', 53),
|
||||
('gomod', 'github.com/go-mockgen/xtools', 'v1.3.2', 54), -- vulnerability
|
||||
('gomod', 'github.com/go-mockgen/xtools', 'v1.3.3', 55), -- vulnerability
|
||||
('gomod', 'github.com/go-mockgen/xtools', 'v1.3.6', 56)
|
||||
`)); err != nil {
|
||||
t.Fatalf("failed to insert references: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// insertUploads populates the lsif_uploads table with the given upload models.
|
||||
func insertUploads(t testing.TB, db database.DB, uploads ...uploadsshared.Upload) {
|
||||
for _, upload := range uploads {
|
||||
if upload.Commit == "" {
|
||||
upload.Commit = makeCommit(upload.ID)
|
||||
}
|
||||
if upload.State == "" {
|
||||
upload.State = "completed"
|
||||
}
|
||||
if upload.RepositoryID == 0 {
|
||||
upload.RepositoryID = 50
|
||||
}
|
||||
if upload.Indexer == "" {
|
||||
upload.Indexer = "lsif-go"
|
||||
}
|
||||
if upload.IndexerVersion == "" {
|
||||
upload.IndexerVersion = "latest"
|
||||
}
|
||||
if upload.UploadedParts == nil {
|
||||
upload.UploadedParts = []int{}
|
||||
}
|
||||
|
||||
// Ensure we have a repo for the inner join in select queries
|
||||
insertRepo(t, db, upload.RepositoryID, upload.RepositoryName)
|
||||
|
||||
query := sqlf.Sprintf(`
|
||||
INSERT INTO lsif_uploads (
|
||||
id,
|
||||
commit,
|
||||
root,
|
||||
uploaded_at,
|
||||
state,
|
||||
failure_message,
|
||||
started_at,
|
||||
finished_at,
|
||||
process_after,
|
||||
num_resets,
|
||||
num_failures,
|
||||
repository_id,
|
||||
indexer,
|
||||
indexer_version,
|
||||
num_parts,
|
||||
uploaded_parts,
|
||||
upload_size,
|
||||
associated_index_id,
|
||||
content_type,
|
||||
should_reindex
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
`,
|
||||
upload.ID,
|
||||
upload.Commit,
|
||||
upload.Root,
|
||||
upload.UploadedAt,
|
||||
upload.State,
|
||||
upload.FailureMessage,
|
||||
upload.StartedAt,
|
||||
upload.FinishedAt,
|
||||
upload.ProcessAfter,
|
||||
upload.NumResets,
|
||||
upload.NumFailures,
|
||||
upload.RepositoryID,
|
||||
upload.Indexer,
|
||||
upload.IndexerVersion,
|
||||
upload.NumParts,
|
||||
pq.Array(upload.UploadedParts),
|
||||
upload.UploadSize,
|
||||
upload.AssociatedIndexID,
|
||||
upload.ContentType,
|
||||
upload.ShouldReindex,
|
||||
)
|
||||
|
||||
if _, err := db.ExecContext(context.Background(), query.Query(sqlf.PostgresBindVar), query.Args()...); err != nil {
|
||||
t.Fatalf("unexpected error while inserting upload: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// makeCommit formats an integer as a 40-character git commit hash.
|
||||
func makeCommit(i int) string {
|
||||
return fmt.Sprintf("%040d", i)
|
||||
}
|
||||
|
||||
// insertRepo creates a repository record with the given id and name. If there is already a repository
|
||||
// with the given identifier, nothing happens
|
||||
func insertRepo(t testing.TB, db database.DB, id int, name string) {
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("n-%d", id)
|
||||
}
|
||||
|
||||
deletedAt := sqlf.Sprintf("NULL")
|
||||
if strings.HasPrefix(name, "DELETED-") {
|
||||
deletedAt = sqlf.Sprintf("%s", time.Unix(1587396557, 0).UTC())
|
||||
}
|
||||
insertRepoQuery := sqlf.Sprintf(
|
||||
`INSERT INTO repo (id, name, deleted_at) VALUES (%s, %s, %s) ON CONFLICT (id) DO NOTHING`,
|
||||
id,
|
||||
name,
|
||||
deletedAt,
|
||||
)
|
||||
if _, err := db.ExecContext(context.Background(), insertRepoQuery.Query(sqlf.PostgresBindVar), insertRepoQuery.Args()...); err != nil {
|
||||
t.Fatalf("unexpected error while upserting repository: %s", err)
|
||||
}
|
||||
|
||||
status := "cloned"
|
||||
if strings.HasPrefix(name, "DELETED-") {
|
||||
status = "not_cloned"
|
||||
}
|
||||
updateGitserverRepoQuery := sqlf.Sprintf(
|
||||
`UPDATE gitserver_repos SET clone_status = %s WHERE repo_id = %s`,
|
||||
status,
|
||||
id,
|
||||
)
|
||||
if _, err := db.ExecContext(context.Background(), updateGitserverRepoQuery.Query(sqlf.PostgresBindVar), updateGitserverRepoQuery.Args()...); err != nil {
|
||||
t.Fatalf("unexpected error while upserting gitserver repository: %s", err)
|
||||
}
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/metrics"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type operations struct {
|
||||
vulnerabilityByID *observation.Operation
|
||||
getVulnerabilitiesByIDs *observation.Operation
|
||||
getVulnerabilities *observation.Operation
|
||||
insertVulnerabilities *observation.Operation
|
||||
vulnerabilityMatchByID *observation.Operation
|
||||
getVulnerabilityMatches *observation.Operation
|
||||
getVulnerabilityMatchesSummaryCount *observation.Operation
|
||||
getVulnerabilityMatchesCountByRepository *observation.Operation
|
||||
scanMatches *observation.Operation
|
||||
}
|
||||
|
||||
var m = new(metrics.SingletonREDMetrics)
|
||||
|
||||
func newOperations(observationCtx *observation.Context) *operations {
|
||||
m := m.Get(func() *metrics.REDMetrics {
|
||||
return metrics.NewREDMetrics(
|
||||
observationCtx.Registerer,
|
||||
"codeintel_sentinel_store",
|
||||
metrics.WithLabels("op"),
|
||||
metrics.WithCountHelp("Total number of method invocations."),
|
||||
)
|
||||
})
|
||||
|
||||
op := func(name string) *observation.Operation {
|
||||
return observationCtx.Operation(observation.Op{
|
||||
Name: fmt.Sprintf("codeintel.sentinel.store.%s", name),
|
||||
MetricLabelValues: []string{name},
|
||||
Metrics: m,
|
||||
})
|
||||
}
|
||||
|
||||
return &operations{
|
||||
vulnerabilityByID: op("VulnerabilityByID"),
|
||||
getVulnerabilitiesByIDs: op("GetVulnerabilitiesByIDs"),
|
||||
getVulnerabilities: op("GetVulnerabilities"),
|
||||
insertVulnerabilities: op("InsertVulnerabilities"),
|
||||
vulnerabilityMatchByID: op("VulnerabilityMatchByID"),
|
||||
getVulnerabilityMatches: op("GetVulnerabilityMatches"),
|
||||
getVulnerabilityMatchesSummaryCount: op("GetVulnerabilityMatchesSummaryCount"),
|
||||
getVulnerabilityMatchesCountByRepository: op("GetVulnerabilityMatchesCountByRepository"),
|
||||
scanMatches: op("ScanMatches"),
|
||||
}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
logger "github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/basestore"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
// Vulnerabilities
|
||||
VulnerabilityByID(ctx context.Context, id int) (_ shared.Vulnerability, _ bool, err error)
|
||||
GetVulnerabilitiesByIDs(ctx context.Context, ids ...int) (_ []shared.Vulnerability, err error)
|
||||
GetVulnerabilities(ctx context.Context, args shared.GetVulnerabilitiesArgs) (_ []shared.Vulnerability, _ int, err error)
|
||||
InsertVulnerabilities(ctx context.Context, vulnerabilities []shared.Vulnerability) (_ int, err error)
|
||||
|
||||
// Vulnerability matches
|
||||
VulnerabilityMatchByID(ctx context.Context, id int) (shared.VulnerabilityMatch, bool, error)
|
||||
GetVulnerabilityMatches(ctx context.Context, args shared.GetVulnerabilityMatchesArgs) ([]shared.VulnerabilityMatch, int, error)
|
||||
GetVulnerabilityMatchesSummaryCount(ctx context.Context) (counts shared.GetVulnerabilityMatchesSummaryCounts, err error)
|
||||
GetVulnerabilityMatchesCountByRepository(ctx context.Context, args shared.GetVulnerabilityMatchesCountByRepositoryArgs) (_ []shared.VulnerabilityMatchesByRepository, _ int, err error)
|
||||
ScanMatches(ctx context.Context, batchSize int) (numReferencesScanned int, numVulnerabilityMatches int, _ error)
|
||||
}
|
||||
|
||||
type store struct {
|
||||
db *basestore.Store
|
||||
logger logger.Logger
|
||||
operations *operations
|
||||
}
|
||||
|
||||
func New(observationCtx *observation.Context, db database.DB) Store {
|
||||
return &store{
|
||||
db: basestore.NewWithHandle(db.Handle()),
|
||||
logger: logger.Scoped("sentinel.store"),
|
||||
operations: newOperations(observationCtx),
|
||||
}
|
||||
}
|
||||
@ -1,519 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/keegancsmith/sqlf"
|
||||
"github.com/lib/pq"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/basestore"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/batch"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/dbutil"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
func (s *store) VulnerabilityByID(ctx context.Context, id int) (_ shared.Vulnerability, _ bool, err error) {
|
||||
ctx, _, endObservation := s.operations.vulnerabilityByID.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("id", id),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
vulnerabilities, _, err := scanVulnerabilitiesAndCount(s.db.Query(ctx, sqlf.Sprintf(getVulnerabilityByIDQuery, id)))
|
||||
if err != nil || len(vulnerabilities) == 0 {
|
||||
return shared.Vulnerability{}, false, err
|
||||
}
|
||||
|
||||
return vulnerabilities[0], true, nil
|
||||
}
|
||||
|
||||
const getVulnerabilityByIDQuery = `
|
||||
SELECT
|
||||
` + vulnerabilityFields + `,
|
||||
` + vulnerabilityAffectedPackageFields + `,
|
||||
` + vulnerabilityAffectedSymbolFields + `,
|
||||
0 AS count
|
||||
FROM vulnerabilities v
|
||||
LEFT JOIN vulnerability_affected_packages vap ON vap.vulnerability_id = v.id
|
||||
LEFT JOIN vulnerability_affected_symbols vas ON vas.vulnerability_affected_package_id = vap.id
|
||||
WHERE v.id = %s
|
||||
ORDER BY vap.id, vas.id
|
||||
`
|
||||
|
||||
const vulnerabilityFields = `
|
||||
v.id,
|
||||
v.source_id,
|
||||
v.summary,
|
||||
v.details,
|
||||
v.cpes,
|
||||
v.cwes,
|
||||
v.aliases,
|
||||
v.related,
|
||||
v.data_source,
|
||||
v.urls,
|
||||
v.severity,
|
||||
v.cvss_vector,
|
||||
v.cvss_score,
|
||||
v.published_at,
|
||||
v.modified_at,
|
||||
v.withdrawn_at
|
||||
`
|
||||
|
||||
const vulnerabilityAffectedPackageFields = `
|
||||
vap.package_name,
|
||||
vap.language,
|
||||
vap.namespace,
|
||||
vap.version_constraint,
|
||||
vap.fixed,
|
||||
vap.fixed_in
|
||||
`
|
||||
|
||||
const vulnerabilityAffectedSymbolFields = `
|
||||
vas.path,
|
||||
vas.symbols
|
||||
`
|
||||
|
||||
func (s *store) GetVulnerabilitiesByIDs(ctx context.Context, ids ...int) (_ []shared.Vulnerability, err error) {
|
||||
ctx, _, endObservation := s.operations.getVulnerabilitiesByIDs.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("numIDs", len(ids)),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
vulnerabilities, _, err := scanVulnerabilitiesAndCount(s.db.Query(ctx, sqlf.Sprintf(getVulnerabilitiesByIDsQuery, pq.Array(ids))))
|
||||
return vulnerabilities, err
|
||||
}
|
||||
|
||||
const getVulnerabilitiesByIDsQuery = `
|
||||
SELECT
|
||||
` + vulnerabilityFields + `,
|
||||
` + vulnerabilityAffectedPackageFields + `,
|
||||
` + vulnerabilityAffectedSymbolFields + `,
|
||||
0 AS count
|
||||
FROM vulnerabilities v
|
||||
LEFT JOIN vulnerability_affected_packages vap ON vap.vulnerability_id = v.id
|
||||
LEFT JOIN vulnerability_affected_symbols vas ON vas.vulnerability_affected_package_id = vap.id
|
||||
WHERE v.id = ANY(%s)
|
||||
ORDER BY v.id, vap.id, vas.id
|
||||
`
|
||||
|
||||
func (s *store) GetVulnerabilities(ctx context.Context, args shared.GetVulnerabilitiesArgs) (_ []shared.Vulnerability, _ int, err error) {
|
||||
ctx, _, endObservation := s.operations.getVulnerabilities.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("limit", args.Limit),
|
||||
attribute.Int("offset", args.Offset),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
return scanVulnerabilitiesAndCount(s.db.Query(ctx, sqlf.Sprintf(getVulnerabilitiesQuery, args.Limit, args.Offset)))
|
||||
}
|
||||
|
||||
const getVulnerabilitiesQuery = `
|
||||
WITH limited_vulnerabilities AS (
|
||||
SELECT
|
||||
` + vulnerabilityFields + `,
|
||||
COUNT(*) OVER() AS count
|
||||
FROM vulnerabilities v
|
||||
ORDER BY id
|
||||
LIMIT %s
|
||||
OFFSET %s
|
||||
)
|
||||
SELECT
|
||||
` + vulnerabilityFields + `,
|
||||
` + vulnerabilityAffectedPackageFields + `,
|
||||
` + vulnerabilityAffectedSymbolFields + `,
|
||||
v.count
|
||||
FROM limited_vulnerabilities v
|
||||
LEFT JOIN vulnerability_affected_packages vap ON vap.vulnerability_id = v.id
|
||||
LEFT JOIN vulnerability_affected_symbols vas ON vas.vulnerability_affected_package_id = vap.id
|
||||
ORDER BY v.id, vap.id, vas.id
|
||||
`
|
||||
|
||||
func (s *store) InsertVulnerabilities(ctx context.Context, vulnerabilities []shared.Vulnerability) (_ int, err error) {
|
||||
ctx, _, endObservation := s.operations.insertVulnerabilities.With(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("numVulnerabilities", len(vulnerabilities)),
|
||||
}})
|
||||
defer endObservation(1, observation.Args{})
|
||||
|
||||
vulnerabilities = canonicalizeVulnerabilities(vulnerabilities)
|
||||
|
||||
var a int
|
||||
err = s.db.WithTransact(ctx, func(tx *basestore.Store) error {
|
||||
if err := tx.Exec(ctx, sqlf.Sprintf(insertVulnerabilitiesTemporaryVulnerabilitiesTableQuery)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec(ctx, sqlf.Sprintf(insertVulnerabilitiesTemporaryVulnerabilityAffectedPackagesTableQuery)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.WithInserter(
|
||||
ctx,
|
||||
tx.Handle(),
|
||||
"t_vulnerabilities",
|
||||
batch.MaxNumPostgresParameters,
|
||||
[]string{
|
||||
"source_id",
|
||||
"summary",
|
||||
"details",
|
||||
"cpes",
|
||||
"cwes",
|
||||
"aliases",
|
||||
"related",
|
||||
"data_source",
|
||||
"urls",
|
||||
"severity",
|
||||
"cvss_vector",
|
||||
"cvss_score",
|
||||
"published_at",
|
||||
"modified_at",
|
||||
"withdrawn_at",
|
||||
},
|
||||
func(inserter *batch.Inserter) error {
|
||||
for _, v := range vulnerabilities {
|
||||
if err := inserter.Insert(
|
||||
ctx,
|
||||
v.SourceID,
|
||||
v.Summary,
|
||||
v.Details,
|
||||
v.CPEs,
|
||||
v.CWEs,
|
||||
v.Aliases,
|
||||
v.Related,
|
||||
v.DataSource,
|
||||
v.URLs,
|
||||
v.Severity,
|
||||
v.CVSSVector,
|
||||
v.CVSSScore,
|
||||
v.PublishedAt,
|
||||
dbutil.NullTime{Time: v.ModifiedAt},
|
||||
dbutil.NullTime{Time: v.WithdrawnAt},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.WithInserter(
|
||||
ctx,
|
||||
tx.Handle(),
|
||||
"t_vulnerability_affected_packages",
|
||||
batch.MaxNumPostgresParameters,
|
||||
[]string{
|
||||
"source_id",
|
||||
"package_name",
|
||||
"language",
|
||||
"namespace",
|
||||
"version_constraint",
|
||||
"fixed",
|
||||
"fixed_in",
|
||||
"affected_symbols",
|
||||
},
|
||||
func(inserter *batch.Inserter) error {
|
||||
for _, v := range vulnerabilities {
|
||||
for _, ap := range v.AffectedPackages {
|
||||
serialized, err := json.Marshal(ap.AffectedSymbols)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := inserter.Insert(
|
||||
ctx,
|
||||
v.SourceID,
|
||||
ap.PackageName,
|
||||
ap.Language,
|
||||
ap.Namespace,
|
||||
ap.VersionConstraint,
|
||||
ap.Fixed,
|
||||
ap.FixedIn,
|
||||
serialized,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count, _, err := basestore.ScanFirstInt(tx.Query(ctx, sqlf.Sprintf(insertVulnerabilitiesUpdateQuery)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec(ctx, sqlf.Sprintf(insertVulnerabilitiesAffectedPackagesUpdateQuery)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec(ctx, sqlf.Sprintf(insertVulnerabilitiesAffectedSymbolsUpdateQuery)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a = count
|
||||
return nil
|
||||
})
|
||||
|
||||
return a, err
|
||||
}
|
||||
|
||||
const insertVulnerabilitiesTemporaryVulnerabilitiesTableQuery = `
|
||||
CREATE TEMPORARY TABLE t_vulnerabilities (
|
||||
source_id TEXT NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
details TEXT NOT NULL,
|
||||
cpes TEXT[] NOT NULL,
|
||||
cwes TEXT[] NOT NULL,
|
||||
aliases TEXT[] NOT NULL,
|
||||
related TEXT[] NOT NULL,
|
||||
data_source TEXT NOT NULL,
|
||||
urls TEXT[] NOT NULL,
|
||||
severity TEXT NOT NULL,
|
||||
cvss_vector TEXT NOT NULL,
|
||||
cvss_score TEXT NOT NULL,
|
||||
published_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
modified_at TIMESTAMP WITH TIME ZONE,
|
||||
withdrawn_at TIMESTAMP WITH TIME ZONE
|
||||
) ON COMMIT DROP
|
||||
`
|
||||
|
||||
const insertVulnerabilitiesTemporaryVulnerabilityAffectedPackagesTableQuery = `
|
||||
CREATE TEMPORARY TABLE t_vulnerability_affected_packages (
|
||||
source_id TEXT NOT NULL,
|
||||
package_name TEXT NOT NULL,
|
||||
language TEXT NOT NULL,
|
||||
namespace TEXT NOT NULL,
|
||||
version_constraint TEXT[] NOT NULL,
|
||||
fixed boolean NOT NULL,
|
||||
fixed_in TEXT,
|
||||
affected_symbols JSON NOT NULL
|
||||
) ON COMMIT DROP
|
||||
`
|
||||
|
||||
const insertVulnerabilitiesUpdateQuery = `
|
||||
WITH ins AS (
|
||||
INSERT INTO vulnerabilities (
|
||||
source_id,
|
||||
summary,
|
||||
details,
|
||||
cpes,
|
||||
cwes,
|
||||
aliases,
|
||||
related,
|
||||
data_source,
|
||||
urls,
|
||||
severity,
|
||||
cvss_vector,
|
||||
cvss_score,
|
||||
published_at,
|
||||
modified_at,
|
||||
withdrawn_at
|
||||
)
|
||||
SELECT
|
||||
source_id,
|
||||
summary,
|
||||
details,
|
||||
cpes,
|
||||
cwes,
|
||||
aliases,
|
||||
related,
|
||||
data_source,
|
||||
urls,
|
||||
severity,
|
||||
cvss_vector,
|
||||
cvss_score,
|
||||
published_at,
|
||||
modified_at,
|
||||
withdrawn_at
|
||||
FROM t_vulnerabilities
|
||||
-- TODO - we'd prefer to update rather than keep first write
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT COUNT(*) FROM ins
|
||||
`
|
||||
|
||||
const insertVulnerabilitiesAffectedPackagesUpdateQuery = `
|
||||
INSERT INTO vulnerability_affected_packages(
|
||||
vulnerability_id,
|
||||
package_name,
|
||||
language,
|
||||
namespace,
|
||||
version_constraint,
|
||||
fixed,
|
||||
fixed_in
|
||||
)
|
||||
SELECT
|
||||
(SELECT v.id FROM vulnerabilities v WHERE v.source_id = vap.source_id),
|
||||
package_name,
|
||||
language,
|
||||
namespace,
|
||||
version_constraint,
|
||||
fixed,
|
||||
fixed_in
|
||||
FROM t_vulnerability_affected_packages vap
|
||||
-- TODO - we'd prefer to update rather than keep first write
|
||||
ON CONFLICT DO NOTHING
|
||||
`
|
||||
|
||||
const insertVulnerabilitiesAffectedSymbolsUpdateQuery = `
|
||||
WITH
|
||||
json_candidates AS (
|
||||
SELECT
|
||||
vap.id,
|
||||
json_array_elements(tvap.affected_symbols) AS affected_symbol
|
||||
FROM t_vulnerability_affected_packages tvap
|
||||
JOIN vulnerability_affected_packages vap ON vap.package_name = tvap.package_name
|
||||
JOIN vulnerabilities v ON v.id = vap.vulnerability_id
|
||||
WHERE
|
||||
v.source_id = tvap.source_id
|
||||
),
|
||||
candidates AS (
|
||||
SELECT
|
||||
c.id,
|
||||
c.affected_symbol->'path'::text AS path,
|
||||
ARRAY(SELECT json_array_elements_text(c.affected_symbol->'symbols'))::text[] AS symbols
|
||||
FROM json_candidates c
|
||||
)
|
||||
INSERT INTO vulnerability_affected_symbols(vulnerability_affected_package_id, path, symbols)
|
||||
SELECT c.id, c.path, c.symbols FROM candidates c
|
||||
-- TODO - we'd prefer to update rather than keep first write
|
||||
ON CONFLICT DO NOTHING
|
||||
`
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
var scanSingleVulnerabilityAndCount = func(s dbutil.Scanner) (v shared.Vulnerability, count int, _ error) {
|
||||
var (
|
||||
vap shared.AffectedPackage
|
||||
vas shared.AffectedSymbol
|
||||
fixedIn string
|
||||
)
|
||||
|
||||
if err := s.Scan(
|
||||
&v.ID,
|
||||
&v.SourceID,
|
||||
&v.Summary,
|
||||
&v.Details,
|
||||
pq.Array(&v.CPEs),
|
||||
pq.Array(&v.CWEs),
|
||||
pq.Array(&v.Aliases),
|
||||
pq.Array(&v.Related),
|
||||
&v.DataSource,
|
||||
pq.Array(&v.URLs),
|
||||
&v.Severity,
|
||||
&v.CVSSVector,
|
||||
&v.CVSSScore,
|
||||
&v.PublishedAt,
|
||||
&v.ModifiedAt,
|
||||
&v.WithdrawnAt,
|
||||
// RHS(s) of left join (may be null)
|
||||
&dbutil.NullString{S: &vap.PackageName},
|
||||
&dbutil.NullString{S: &vap.Language},
|
||||
&dbutil.NullString{S: &vap.Namespace},
|
||||
pq.Array(&vap.VersionConstraint),
|
||||
&dbutil.NullBool{B: &vap.Fixed},
|
||||
&dbutil.NullString{S: &fixedIn},
|
||||
&dbutil.NullString{S: &vas.Path},
|
||||
pq.Array(vas.Symbols),
|
||||
&count,
|
||||
); err != nil {
|
||||
return shared.Vulnerability{}, 0, err
|
||||
}
|
||||
|
||||
if fixedIn != "" {
|
||||
vap.FixedIn = &fixedIn
|
||||
}
|
||||
if vas.Path != "" {
|
||||
vap.AffectedSymbols = append(vap.AffectedSymbols, vas)
|
||||
}
|
||||
if vap.PackageName != "" {
|
||||
v.AffectedPackages = append(v.AffectedPackages, vap)
|
||||
}
|
||||
|
||||
return v, count, nil
|
||||
}
|
||||
|
||||
var flattenPackages = func(packages []shared.AffectedPackage) []shared.AffectedPackage {
|
||||
flattened := []shared.AffectedPackage{}
|
||||
for _, pkg := range packages {
|
||||
i := len(flattened) - 1
|
||||
if len(flattened) == 0 || flattened[i].Namespace != pkg.Namespace || flattened[i].Language != pkg.Language || flattened[i].PackageName != pkg.PackageName {
|
||||
flattened = append(flattened, pkg)
|
||||
} else {
|
||||
flattened[i].AffectedSymbols = append(flattened[i].AffectedSymbols, pkg.AffectedSymbols...)
|
||||
}
|
||||
}
|
||||
|
||||
return flattened
|
||||
}
|
||||
|
||||
var flattenVulnerabilities = func(vs []shared.Vulnerability) []shared.Vulnerability {
|
||||
flattened := []shared.Vulnerability{}
|
||||
for _, v := range vs {
|
||||
i := len(flattened) - 1
|
||||
if len(flattened) == 0 || flattened[i].ID != v.ID {
|
||||
flattened = append(flattened, v)
|
||||
} else {
|
||||
flattened[i].AffectedPackages = flattenPackages(append(flattened[i].AffectedPackages, v.AffectedPackages...))
|
||||
}
|
||||
}
|
||||
|
||||
return flattened
|
||||
}
|
||||
|
||||
var scanVulnerabilitiesAndCount = func(rows basestore.Rows, queryErr error) ([]shared.Vulnerability, int, error) {
|
||||
values, totalCount, err := basestore.NewSliceWithCountScanner(func(s dbutil.Scanner) (shared.Vulnerability, int, error) {
|
||||
return scanSingleVulnerabilityAndCount(s)
|
||||
})(rows, queryErr)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return flattenVulnerabilities(values), totalCount, nil
|
||||
}
|
||||
|
||||
func canonicalizeVulnerabilities(vs []shared.Vulnerability) []shared.Vulnerability {
|
||||
for i, v := range vs {
|
||||
vs[i] = canonicalizeVulnerability(v)
|
||||
}
|
||||
|
||||
return vs
|
||||
}
|
||||
|
||||
func canonicalizeVulnerability(v shared.Vulnerability) shared.Vulnerability {
|
||||
if v.CPEs == nil {
|
||||
v.CPEs = []string{}
|
||||
}
|
||||
if v.CWEs == nil {
|
||||
v.CWEs = []string{}
|
||||
}
|
||||
if v.Aliases == nil {
|
||||
v.Aliases = []string{}
|
||||
}
|
||||
if v.Related == nil {
|
||||
v.Related = []string{}
|
||||
}
|
||||
if v.URLs == nil {
|
||||
v.URLs = []string{}
|
||||
}
|
||||
for i, ap := range v.AffectedPackages {
|
||||
v.AffectedPackages[i] = canonicalizeAffectedPackage(ap)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func canonicalizeAffectedPackage(ap shared.AffectedPackage) shared.AffectedPackage {
|
||||
if ap.VersionConstraint == nil {
|
||||
ap.VersionConstraint = []string{}
|
||||
}
|
||||
if ap.AffectedSymbols == nil {
|
||||
ap.AffectedSymbols = []shared.AffectedSymbol{}
|
||||
}
|
||||
|
||||
return ap
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/sourcegraph/log/logtest"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/dbtest"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
var badConfig = shared.AffectedPackage{
|
||||
Language: "go",
|
||||
PackageName: "go-nacelle/config",
|
||||
VersionConstraint: []string{"<= v1.2.5"},
|
||||
}
|
||||
|
||||
var testVulnerabilities = []shared.Vulnerability{
|
||||
// IDs assumed by insertion order
|
||||
{ID: 1, SourceID: "CVE-ABC", AffectedPackages: []shared.AffectedPackage{badConfig}},
|
||||
{ID: 2, SourceID: "CVE-DEF"},
|
||||
{ID: 3, SourceID: "CVE-GHI"},
|
||||
{ID: 4, SourceID: "CVE-JKL"},
|
||||
{ID: 5, SourceID: "CVE-MNO"},
|
||||
{ID: 6, SourceID: "CVE-PQR"},
|
||||
{ID: 7, SourceID: "CVE-STU"},
|
||||
{ID: 8, SourceID: "CVE-VWX"},
|
||||
{ID: 9, SourceID: "CVE-Y&Z"},
|
||||
}
|
||||
|
||||
func TestVulnerabilityByID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(t))
|
||||
store := New(observation.TestContextTB(t), db)
|
||||
|
||||
if _, err := store.InsertVulnerabilities(ctx, testVulnerabilities); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
vulnerability, ok, err := store.VulnerabilityByID(ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get vulnerability by id: %s", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("unexpected vulnerability to exist")
|
||||
}
|
||||
if diff := cmp.Diff(canonicalizeVulnerability(testVulnerabilities[1]), vulnerability); diff != "" {
|
||||
t.Errorf("unexpected vulnerability (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVulnerabilitiesByIDs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(t))
|
||||
store := New(observation.TestContextTB(t), db)
|
||||
|
||||
if _, err := store.InsertVulnerabilities(ctx, testVulnerabilities); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
vulnerabilities, err := store.GetVulnerabilitiesByIDs(ctx, 2, 3, 4)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get vulnerability by id: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(canonicalizeVulnerabilities(testVulnerabilities[1:4]), vulnerabilities); diff != "" {
|
||||
t.Errorf("unexpected vulnerabilities (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVulnerabilities(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(t))
|
||||
store := New(observation.TestContextTB(t), db)
|
||||
|
||||
if _, err := store.InsertVulnerabilities(ctx, testVulnerabilities); err != nil {
|
||||
t.Fatalf("unexpected error inserting vulnerabilities: %s", err)
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
expectedSourceIDs []string
|
||||
}
|
||||
testCases := []testCase{
|
||||
{
|
||||
name: "all",
|
||||
expectedSourceIDs: []string{"CVE-ABC", "CVE-DEF", "CVE-GHI", "CVE-JKL", "CVE-MNO", "CVE-PQR", "CVE-STU", "CVE-VWX", "CVE-Y&Z"},
|
||||
},
|
||||
}
|
||||
|
||||
runTest := func(testCase testCase, lo, hi int) (errors int) {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
vulnerabilities, totalCount, err := store.GetVulnerabilities(ctx, shared.GetVulnerabilitiesArgs{
|
||||
Limit: 3,
|
||||
Offset: lo,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting vulnerabilities: %s", err)
|
||||
}
|
||||
if totalCount != len(testCase.expectedSourceIDs) {
|
||||
t.Errorf("unexpected total count. want=%d have=%d", len(testCase.expectedSourceIDs), totalCount)
|
||||
}
|
||||
|
||||
if totalCount != 0 {
|
||||
var ids []string
|
||||
for _, vulnerability := range vulnerabilities {
|
||||
ids = append(ids, vulnerability.SourceID)
|
||||
}
|
||||
if diff := cmp.Diff(testCase.expectedSourceIDs[lo:hi], ids); diff != "" {
|
||||
t.Errorf("unexpected vulnerability ids at offset %d-%d (-want +got):\n%s", lo, hi, diff)
|
||||
errors++
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
if n := len(testCase.expectedSourceIDs); n == 0 {
|
||||
runTest(testCase, 0, 0)
|
||||
} else {
|
||||
for lo := range n {
|
||||
if numErrors := runTest(testCase, lo, int(math.Min(float64(lo)+3, float64(n)))); numErrors > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package sentinel
|
||||
|
||||
import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type operations struct {
|
||||
}
|
||||
|
||||
// var m = new(metrics.SingletonREDMetrics)
|
||||
|
||||
func newOperations(observationCtx *observation.Context) *operations {
|
||||
// redMetrics := m.Get(func() *metrics.REDMetrics {
|
||||
// return metrics.NewREDMetrics(
|
||||
// observationCtx.Registerer,
|
||||
// "codeintel_sentinel",
|
||||
// metrics.WithLabels("op"),
|
||||
// metrics.WithCountHelp("Total number of method invocations."),
|
||||
// )
|
||||
// })
|
||||
|
||||
// op := func(name string) *observation.Operation {
|
||||
// return observationCtx.Operation(observation.Op{
|
||||
// Name: fmt.Sprintf("codeintel.sentinel.%s", name),
|
||||
// MetricLabelValues: []string{name},
|
||||
// Metrics: redMetrics,
|
||||
// })
|
||||
// }
|
||||
|
||||
return &operations{}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
package sentinel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/internal/store"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
store store.Store
|
||||
operations *operations
|
||||
}
|
||||
|
||||
func newService(
|
||||
observationCtx *observation.Context,
|
||||
store store.Store,
|
||||
) *Service {
|
||||
return &Service{
|
||||
store: store,
|
||||
operations: newOperations(observationCtx),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) GetVulnerabilitiesByIDs(ctx context.Context, ids ...int) ([]shared.Vulnerability, error) {
|
||||
return s.store.GetVulnerabilitiesByIDs(ctx, ids...)
|
||||
}
|
||||
|
||||
func (s *Service) VulnerabilityByID(ctx context.Context, id int) (shared.Vulnerability, bool, error) {
|
||||
return s.store.VulnerabilityByID(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) VulnerabilityMatchByID(ctx context.Context, id int) (shared.VulnerabilityMatch, bool, error) {
|
||||
return s.store.VulnerabilityMatchByID(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) GetVulnerabilities(ctx context.Context, args shared.GetVulnerabilitiesArgs) ([]shared.Vulnerability, int, error) {
|
||||
return s.store.GetVulnerabilities(ctx, args)
|
||||
}
|
||||
|
||||
func (s *Service) GetVulnerabilityMatches(ctx context.Context, args shared.GetVulnerabilityMatchesArgs) ([]shared.VulnerabilityMatch, int, error) {
|
||||
return s.store.GetVulnerabilityMatches(ctx, args)
|
||||
}
|
||||
|
||||
func (s *Service) GetVulnerabilityMatchesSummaryCounts(ctx context.Context) (shared.GetVulnerabilityMatchesSummaryCounts, error) {
|
||||
return s.store.GetVulnerabilityMatchesSummaryCount(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) GetVulnerabilityMatchesCountByRepository(ctx context.Context, args shared.GetVulnerabilityMatchesCountByRepositoryArgs) ([]shared.VulnerabilityMatchesByRepository, int, error) {
|
||||
return s.store.GetVulnerabilityMatchesCountByRepository(ctx, args)
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "shared",
|
||||
srcs = ["types.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
@ -1,92 +0,0 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Vulnerability struct {
|
||||
ID int // internal ID
|
||||
SourceID string // external ID
|
||||
Summary string
|
||||
Details string
|
||||
CPEs []string
|
||||
CWEs []string
|
||||
Aliases []string
|
||||
Related []string
|
||||
DataSource string
|
||||
URLs []string
|
||||
Severity string
|
||||
CVSSVector string
|
||||
CVSSScore string
|
||||
PublishedAt time.Time
|
||||
ModifiedAt *time.Time
|
||||
WithdrawnAt *time.Time
|
||||
AffectedPackages []AffectedPackage
|
||||
}
|
||||
|
||||
func (v Vulnerability) RecordID() int {
|
||||
return v.ID
|
||||
}
|
||||
|
||||
func (v Vulnerability) RecordUID() string {
|
||||
return strconv.Itoa(v.ID)
|
||||
}
|
||||
|
||||
// Data that varies across instances of a vulnerability
|
||||
// Need to decide if this will be flat inside Vulnerability (and have multiple duplicate vulns)
|
||||
// or a separate struct/table
|
||||
type AffectedPackage struct {
|
||||
PackageName string
|
||||
Language string
|
||||
Namespace string
|
||||
VersionConstraint []string
|
||||
Fixed bool
|
||||
FixedIn *string
|
||||
AffectedSymbols []AffectedSymbol
|
||||
}
|
||||
|
||||
type AffectedSymbol struct {
|
||||
Path string `json:"path"`
|
||||
Symbols []string `json:"symbols"`
|
||||
}
|
||||
|
||||
type VulnerabilityMatch struct {
|
||||
ID int
|
||||
UploadID int
|
||||
VulnerabilityID int
|
||||
AffectedPackage AffectedPackage
|
||||
}
|
||||
|
||||
type GetVulnerabilitiesArgs struct {
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type GetVulnerabilityMatchesArgs struct {
|
||||
Limit int
|
||||
Offset int
|
||||
Severity string
|
||||
Language string
|
||||
RepositoryName string
|
||||
}
|
||||
|
||||
type GetVulnerabilityMatchesSummaryCounts struct {
|
||||
Critical int32
|
||||
High int32
|
||||
Medium int32
|
||||
Low int32
|
||||
Repositories int32
|
||||
}
|
||||
|
||||
type GetVulnerabilityMatchesCountByRepositoryArgs struct {
|
||||
RepositoryName string
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type VulnerabilityMatchesByRepository struct {
|
||||
ID int
|
||||
RepositoryName string
|
||||
MatchCount int32
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "graphql",
|
||||
srcs = [
|
||||
"dataloader.go",
|
||||
"iface.go",
|
||||
"observability.go",
|
||||
"root_resolver.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/transport/graphql",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/codeintel/resolvers",
|
||||
"//internal/codeintel/sentinel/shared",
|
||||
"//internal/codeintel/shared/resolvers/dataloader",
|
||||
"//internal/codeintel/shared/resolvers/gitresolvers",
|
||||
"//internal/codeintel/uploads/transport/graphql",
|
||||
"//internal/gqlutil",
|
||||
"//internal/metrics",
|
||||
"//internal/observation",
|
||||
"//lib/pointers",
|
||||
"@com_github_graph_gophers_graphql_go//:graphql-go",
|
||||
"@io_opentelemetry_go_otel//attribute",
|
||||
],
|
||||
)
|
||||
@ -1,23 +0,0 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/shared/resolvers/dataloader"
|
||||
uploadsgraphql "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/transport/graphql"
|
||||
)
|
||||
|
||||
type (
|
||||
VulnerabilityLoaderFactory = *dataloader.LoaderFactory[int, shared.Vulnerability]
|
||||
VulnerabilityLoader = *dataloader.Loader[int, shared.Vulnerability]
|
||||
)
|
||||
|
||||
func NewVulnerabilityLoaderFactory(sentinelSvc SentinelService) VulnerabilityLoaderFactory {
|
||||
return dataloader.NewLoaderFactory[int, shared.Vulnerability](dataloader.BackingServiceFunc[int, shared.Vulnerability](sentinelSvc.GetVulnerabilitiesByIDs))
|
||||
}
|
||||
|
||||
func PresubmitMatches(vulnerabilityLoader VulnerabilityLoader, uploadLoader uploadsgraphql.UploadLoader, matches ...shared.VulnerabilityMatch) {
|
||||
for _, match := range matches {
|
||||
vulnerabilityLoader.Presubmit(match.VulnerabilityID)
|
||||
uploadLoader.Presubmit(match.UploadID)
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
)
|
||||
|
||||
type SentinelService interface {
|
||||
GetVulnerabilities(ctx context.Context, args shared.GetVulnerabilitiesArgs) ([]shared.Vulnerability, int, error)
|
||||
GetVulnerabilitiesByIDs(ctx context.Context, ids ...int) ([]shared.Vulnerability, error)
|
||||
VulnerabilityByID(ctx context.Context, id int) (shared.Vulnerability, bool, error)
|
||||
|
||||
GetVulnerabilityMatches(ctx context.Context, args shared.GetVulnerabilityMatchesArgs) ([]shared.VulnerabilityMatch, int, error)
|
||||
VulnerabilityMatchByID(ctx context.Context, id int) (shared.VulnerabilityMatch, bool, error)
|
||||
GetVulnerabilityMatchesSummaryCounts(ctx context.Context) (shared.GetVulnerabilityMatchesSummaryCounts, error)
|
||||
GetVulnerabilityMatchesCountByRepository(ctx context.Context, args shared.GetVulnerabilityMatchesCountByRepositoryArgs) (_ []shared.VulnerabilityMatchesByRepository, _ int, err error)
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/metrics"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
type operations struct {
|
||||
getVulnerabilities *observation.Operation
|
||||
vulnerabilityByID *observation.Operation
|
||||
getMatches *observation.Operation
|
||||
vulnerabilityMatchByID *observation.Operation
|
||||
vulnerabilityMatchesSummaryCounts *observation.Operation
|
||||
vulnerabilityMatchesCountByRepository *observation.Operation
|
||||
}
|
||||
|
||||
func newOperations(observationCtx *observation.Context) *operations {
|
||||
m := metrics.NewREDMetrics(
|
||||
observationCtx.Registerer,
|
||||
"codeintel_sentinel_transport_graphql",
|
||||
metrics.WithLabels("op"),
|
||||
metrics.WithCountHelp("Total number of method invocations."),
|
||||
)
|
||||
|
||||
op := func(name string) *observation.Operation {
|
||||
return observationCtx.Operation(observation.Op{
|
||||
Name: fmt.Sprintf("codeintel.sentinel.transport.graphql.%s", name),
|
||||
MetricLabelValues: []string{name},
|
||||
Metrics: m,
|
||||
})
|
||||
}
|
||||
|
||||
return &operations{
|
||||
getVulnerabilities: op("Vulnerabilities"),
|
||||
vulnerabilityByID: op("VulnerabilityByID"),
|
||||
getMatches: op("Matches"),
|
||||
vulnerabilityMatchByID: op("VulnerabilityMatchByID"),
|
||||
vulnerabilityMatchesSummaryCounts: op("VulnerabilityMatchesSummaryCounts"),
|
||||
vulnerabilityMatchesCountByRepository: op("VulnerabilityMatchesCountByRepository"),
|
||||
}
|
||||
}
|
||||
@ -1,388 +0,0 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/graph-gophers/graphql-go"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
resolverstubs "github.com/sourcegraph/sourcegraph/internal/codeintel/resolvers"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/shared/resolvers/gitresolvers"
|
||||
uploadsgraphql "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/transport/graphql"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gqlutil"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
"github.com/sourcegraph/sourcegraph/lib/pointers"
|
||||
)
|
||||
|
||||
type rootResolver struct {
|
||||
sentinelSvc SentinelService
|
||||
vulnerabilityLoaderFactory VulnerabilityLoaderFactory
|
||||
uploadLoaderFactory uploadsgraphql.UploadLoaderFactory
|
||||
indexLoaderFactory uploadsgraphql.IndexLoaderFactory
|
||||
locationResolverFactory *gitresolvers.CachedLocationResolverFactory
|
||||
preciseIndexResolverFactory *uploadsgraphql.PreciseIndexResolverFactory
|
||||
operations *operations
|
||||
}
|
||||
|
||||
func NewRootResolver(
|
||||
observationCtx *observation.Context,
|
||||
sentinelSvc SentinelService,
|
||||
uploadLoaderFactory uploadsgraphql.UploadLoaderFactory,
|
||||
indexLoaderFactory uploadsgraphql.IndexLoaderFactory,
|
||||
locationResolverFactory *gitresolvers.CachedLocationResolverFactory,
|
||||
preciseIndexResolverFactory *uploadsgraphql.PreciseIndexResolverFactory,
|
||||
) resolverstubs.SentinelServiceResolver {
|
||||
return &rootResolver{
|
||||
sentinelSvc: sentinelSvc,
|
||||
vulnerabilityLoaderFactory: NewVulnerabilityLoaderFactory(sentinelSvc),
|
||||
uploadLoaderFactory: uploadLoaderFactory,
|
||||
indexLoaderFactory: indexLoaderFactory,
|
||||
locationResolverFactory: locationResolverFactory,
|
||||
preciseIndexResolverFactory: preciseIndexResolverFactory,
|
||||
operations: newOperations(observationCtx),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rootResolver) Vulnerabilities(ctx context.Context, args resolverstubs.GetVulnerabilitiesArgs) (_ resolverstubs.VulnerabilityConnectionResolver, err error) {
|
||||
ctx, _, endObservation := r.operations.getVulnerabilities.WithErrors(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("first", int(pointers.Deref(args.First, 0))),
|
||||
attribute.String("after", pointers.Deref(args.After, "")),
|
||||
}})
|
||||
endObservation.OnCancel(ctx, 1, observation.Args{})
|
||||
|
||||
limit, offset, err := args.ParseLimitOffset(50)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vulnerabilities, totalCount, err := r.sentinelSvc.GetVulnerabilities(ctx, shared.GetVulnerabilitiesArgs{
|
||||
Limit: int(limit),
|
||||
Offset: int(offset),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resolvers []resolverstubs.VulnerabilityResolver
|
||||
for _, v := range vulnerabilities {
|
||||
resolvers = append(resolvers, &vulnerabilityResolver{v: v})
|
||||
}
|
||||
|
||||
return resolverstubs.NewTotalCountConnectionResolver(resolvers, offset, int32(totalCount)), nil
|
||||
}
|
||||
|
||||
func (r *rootResolver) VulnerabilityMatches(ctx context.Context, args resolverstubs.GetVulnerabilityMatchesArgs) (_ resolverstubs.VulnerabilityMatchConnectionResolver, err error) {
|
||||
ctx, errTracer, endObservation := r.operations.getMatches.WithErrors(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.Int("first", int(pointers.Deref(args.First, 0))),
|
||||
attribute.String("after", pointers.Deref(args.After, "")),
|
||||
}})
|
||||
endObservation.OnCancel(ctx, 1, observation.Args{})
|
||||
|
||||
limit, offset, err := args.ParseLimitOffset(50)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
language := ""
|
||||
if args.Language != nil {
|
||||
language = *args.Language
|
||||
}
|
||||
|
||||
severity := ""
|
||||
if args.Severity != nil {
|
||||
severity = *args.Severity
|
||||
}
|
||||
|
||||
repositoryName := ""
|
||||
if args.RepositoryName != nil {
|
||||
repositoryName = *args.RepositoryName
|
||||
}
|
||||
|
||||
matches, totalCount, err := r.sentinelSvc.GetVulnerabilityMatches(ctx, shared.GetVulnerabilityMatchesArgs{
|
||||
Limit: int(limit),
|
||||
Offset: int(offset),
|
||||
Language: language,
|
||||
Severity: severity,
|
||||
RepositoryName: repositoryName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre-submit vulnerability and upload ids for loading
|
||||
vulnerabilityLoader := r.vulnerabilityLoaderFactory.Create()
|
||||
uploadLoader := r.uploadLoaderFactory.Create()
|
||||
PresubmitMatches(vulnerabilityLoader, uploadLoader, matches...)
|
||||
|
||||
// No data to load for associated indexes or git data (yet)
|
||||
indexLoader := r.indexLoaderFactory.Create()
|
||||
locationResolver := r.locationResolverFactory.Create()
|
||||
|
||||
var resolvers []resolverstubs.VulnerabilityMatchResolver
|
||||
for _, m := range matches {
|
||||
resolvers = append(resolvers, &vulnerabilityMatchResolver{
|
||||
uploadLoader: uploadLoader,
|
||||
indexLoader: indexLoader,
|
||||
locationResolver: locationResolver,
|
||||
errTracer: errTracer,
|
||||
vulnerabilityLoader: vulnerabilityLoader,
|
||||
m: m,
|
||||
})
|
||||
}
|
||||
|
||||
return resolverstubs.NewTotalCountConnectionResolver(resolvers, offset, int32(totalCount)), nil
|
||||
}
|
||||
|
||||
func (r *rootResolver) VulnerabilityMatchesCountByRepository(ctx context.Context, args resolverstubs.GetVulnerabilityMatchCountByRepositoryArgs) (_ resolverstubs.VulnerabilityMatchCountByRepositoryConnectionResolver, err error) {
|
||||
ctx, _, endObservation := r.operations.vulnerabilityMatchesCountByRepository.WithErrors(ctx, &err, observation.Args{})
|
||||
endObservation.OnCancel(ctx, 1, observation.Args{})
|
||||
|
||||
limit, offset, err := args.ParseLimitOffset(50)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repositoryName := ""
|
||||
if args.RepositoryName != nil {
|
||||
repositoryName = *args.RepositoryName
|
||||
}
|
||||
|
||||
vulnerabilityCounts, totalCount, err := r.sentinelSvc.GetVulnerabilityMatchesCountByRepository(ctx, shared.GetVulnerabilityMatchesCountByRepositoryArgs{
|
||||
Limit: int(limit),
|
||||
Offset: int(offset),
|
||||
RepositoryName: repositoryName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resolvers []resolverstubs.VulnerabilityMatchCountByRepositoryResolver
|
||||
for _, v := range vulnerabilityCounts {
|
||||
resolvers = append(resolvers, &vulnerabilityMatchCountByRepositoryResolver{v: v})
|
||||
}
|
||||
|
||||
return resolverstubs.NewTotalCountConnectionResolver(resolvers, offset, int32(totalCount)), nil
|
||||
}
|
||||
|
||||
func (r *rootResolver) VulnerabilityByID(ctx context.Context, vulnerabilityID graphql.ID) (_ resolverstubs.VulnerabilityResolver, err error) {
|
||||
ctx, _, endObservation := r.operations.vulnerabilityByID.WithErrors(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.String("vulnerabilityID", string(vulnerabilityID)),
|
||||
}})
|
||||
endObservation.OnCancel(ctx, 1, observation.Args{})
|
||||
|
||||
id, err := resolverstubs.UnmarshalID[int](vulnerabilityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vulnerability, ok, err := r.sentinelSvc.VulnerabilityByID(ctx, id)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vulnerabilityResolver{vulnerability}, nil
|
||||
}
|
||||
|
||||
func (r *rootResolver) VulnerabilityMatchByID(ctx context.Context, vulnerabilityMatchID graphql.ID) (_ resolverstubs.VulnerabilityMatchResolver, err error) {
|
||||
ctx, errTracer, endObservation := r.operations.vulnerabilityMatchByID.WithErrors(ctx, &err, observation.Args{Attrs: []attribute.KeyValue{
|
||||
attribute.String("vulnerabilityMatchID", string(vulnerabilityMatchID)),
|
||||
}})
|
||||
endObservation.OnCancel(ctx, 1, observation.Args{})
|
||||
|
||||
id, err := resolverstubs.UnmarshalID[int](vulnerabilityMatchID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
match, ok, err := r.sentinelSvc.VulnerabilityMatchByID(ctx, id)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre-submit vulnerability and upload ids for loading
|
||||
vulnerabilityLoader := r.vulnerabilityLoaderFactory.Create()
|
||||
uploadLoader := r.uploadLoaderFactory.Create()
|
||||
PresubmitMatches(vulnerabilityLoader, uploadLoader, match)
|
||||
|
||||
// No data to load for associated indexes or git data (yet)
|
||||
indexLoader := r.indexLoaderFactory.Create()
|
||||
locationResolver := r.locationResolverFactory.Create()
|
||||
|
||||
return &vulnerabilityMatchResolver{
|
||||
uploadLoader: uploadLoader,
|
||||
indexLoader: indexLoader,
|
||||
locationResolver: locationResolver,
|
||||
|
||||
errTracer: errTracer,
|
||||
vulnerabilityLoader: vulnerabilityLoader,
|
||||
m: match,
|
||||
preciseIndexResolverFactory: r.preciseIndexResolverFactory,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *rootResolver) VulnerabilityMatchesSummaryCounts(ctx context.Context) (_ resolverstubs.VulnerabilityMatchesSummaryCountResolver, err error) {
|
||||
ctx, _, endObservation := r.operations.vulnerabilityMatchesSummaryCounts.WithErrors(ctx, &err, observation.Args{})
|
||||
endObservation.OnCancel(ctx, 1, observation.Args{})
|
||||
|
||||
counts, err := r.sentinelSvc.GetVulnerabilityMatchesSummaryCounts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vulnerabilityMatchesSummaryCountResolver{
|
||||
critical: counts.Critical,
|
||||
high: counts.High,
|
||||
medium: counts.Medium,
|
||||
low: counts.Low,
|
||||
repository: counts.Repositories,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
type vulnerabilityResolver struct {
|
||||
v shared.Vulnerability
|
||||
}
|
||||
|
||||
func (r *vulnerabilityResolver) ID() graphql.ID {
|
||||
return resolverstubs.MarshalID("Vulnerability", r.v.ID)
|
||||
}
|
||||
func (r *vulnerabilityResolver) SourceID() string { return r.v.SourceID }
|
||||
func (r *vulnerabilityResolver) Summary() string { return r.v.Summary }
|
||||
func (r *vulnerabilityResolver) Details() string { return r.v.Details }
|
||||
func (r *vulnerabilityResolver) CPEs() []string { return r.v.CPEs }
|
||||
func (r *vulnerabilityResolver) CWEs() []string { return r.v.CWEs }
|
||||
func (r *vulnerabilityResolver) Aliases() []string { return r.v.Aliases }
|
||||
func (r *vulnerabilityResolver) Related() []string { return r.v.Related }
|
||||
func (r *vulnerabilityResolver) DataSource() string { return r.v.DataSource }
|
||||
func (r *vulnerabilityResolver) URLs() []string { return r.v.URLs }
|
||||
func (r *vulnerabilityResolver) Severity() string { return r.v.Severity }
|
||||
func (r *vulnerabilityResolver) CVSSVector() string { return r.v.CVSSVector }
|
||||
func (r *vulnerabilityResolver) CVSSScore() string { return r.v.CVSSScore }
|
||||
|
||||
func (r *vulnerabilityResolver) Published() gqlutil.DateTime {
|
||||
return *gqlutil.DateTimeOrNil(&r.v.PublishedAt)
|
||||
}
|
||||
|
||||
func (r *vulnerabilityResolver) Modified() *gqlutil.DateTime {
|
||||
return gqlutil.DateTimeOrNil(r.v.ModifiedAt)
|
||||
}
|
||||
|
||||
func (r *vulnerabilityResolver) Withdrawn() *gqlutil.DateTime {
|
||||
return gqlutil.DateTimeOrNil(r.v.WithdrawnAt)
|
||||
}
|
||||
|
||||
func (r *vulnerabilityResolver) AffectedPackages() []resolverstubs.VulnerabilityAffectedPackageResolver {
|
||||
var resolvers []resolverstubs.VulnerabilityAffectedPackageResolver
|
||||
for _, p := range r.v.AffectedPackages {
|
||||
resolvers = append(resolvers, &vulnerabilityAffectedPackageResolver{
|
||||
p: p,
|
||||
})
|
||||
}
|
||||
|
||||
return resolvers
|
||||
}
|
||||
|
||||
type vulnerabilityAffectedPackageResolver struct {
|
||||
p shared.AffectedPackage
|
||||
}
|
||||
|
||||
func (r *vulnerabilityAffectedPackageResolver) PackageName() string { return r.p.PackageName }
|
||||
func (r *vulnerabilityAffectedPackageResolver) Language() string { return r.p.Language }
|
||||
func (r *vulnerabilityAffectedPackageResolver) Namespace() string { return r.p.Namespace }
|
||||
func (r *vulnerabilityAffectedPackageResolver) VersionConstraint() []string {
|
||||
return r.p.VersionConstraint
|
||||
}
|
||||
func (r *vulnerabilityAffectedPackageResolver) Fixed() bool { return r.p.Fixed }
|
||||
func (r *vulnerabilityAffectedPackageResolver) FixedIn() *string { return r.p.FixedIn }
|
||||
|
||||
func (r *vulnerabilityAffectedPackageResolver) AffectedSymbols() []resolverstubs.VulnerabilityAffectedSymbolResolver {
|
||||
var resolvers []resolverstubs.VulnerabilityAffectedSymbolResolver
|
||||
for _, s := range r.p.AffectedSymbols {
|
||||
resolvers = append(resolvers, &vulnerabilityAffectedSymbolResolver{
|
||||
s: s,
|
||||
})
|
||||
}
|
||||
|
||||
return resolvers
|
||||
}
|
||||
|
||||
type vulnerabilityAffectedSymbolResolver struct {
|
||||
s shared.AffectedSymbol
|
||||
}
|
||||
|
||||
func (r *vulnerabilityAffectedSymbolResolver) Path() string { return r.s.Path }
|
||||
func (r *vulnerabilityAffectedSymbolResolver) Symbols() []string { return r.s.Symbols }
|
||||
|
||||
type vulnerabilityMatchResolver struct {
|
||||
uploadLoader uploadsgraphql.UploadLoader
|
||||
indexLoader uploadsgraphql.IndexLoader
|
||||
locationResolver *gitresolvers.CachedLocationResolver
|
||||
errTracer *observation.ErrCollector
|
||||
vulnerabilityLoader VulnerabilityLoader
|
||||
m shared.VulnerabilityMatch
|
||||
preciseIndexResolverFactory *uploadsgraphql.PreciseIndexResolverFactory
|
||||
}
|
||||
|
||||
func (r *vulnerabilityMatchResolver) ID() graphql.ID {
|
||||
return resolverstubs.MarshalID("VulnerabilityMatch", r.m.ID)
|
||||
}
|
||||
|
||||
func (r *vulnerabilityMatchResolver) Vulnerability(ctx context.Context) (resolverstubs.VulnerabilityResolver, error) {
|
||||
vulnerability, ok, err := r.vulnerabilityLoader.GetByID(ctx, r.m.VulnerabilityID)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vulnerabilityResolver{v: vulnerability}, nil
|
||||
}
|
||||
|
||||
func (r *vulnerabilityMatchResolver) AffectedPackage(ctx context.Context) (resolverstubs.VulnerabilityAffectedPackageResolver, error) {
|
||||
return &vulnerabilityAffectedPackageResolver{r.m.AffectedPackage}, nil
|
||||
}
|
||||
|
||||
func (r *vulnerabilityMatchResolver) PreciseIndex(ctx context.Context) (resolverstubs.PreciseIndexResolver, error) {
|
||||
upload, ok, err := r.uploadLoader.GetByID(ctx, r.m.UploadID)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.preciseIndexResolverFactory.Create(ctx, r.uploadLoader, r.indexLoader, r.locationResolver, r.errTracer, &upload, nil)
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
type vulnerabilityMatchesSummaryCountResolver struct {
|
||||
critical int32
|
||||
high int32
|
||||
medium int32
|
||||
low int32
|
||||
repository int32
|
||||
}
|
||||
|
||||
func (v *vulnerabilityMatchesSummaryCountResolver) Critical() int32 { return v.critical }
|
||||
func (v *vulnerabilityMatchesSummaryCountResolver) High() int32 { return v.high }
|
||||
func (v *vulnerabilityMatchesSummaryCountResolver) Medium() int32 { return v.medium }
|
||||
func (v *vulnerabilityMatchesSummaryCountResolver) Low() int32 { return v.low }
|
||||
func (v *vulnerabilityMatchesSummaryCountResolver) Repository() int32 {
|
||||
return v.repository
|
||||
}
|
||||
|
||||
type vulnerabilityMatchCountByRepositoryResolver struct {
|
||||
v shared.VulnerabilityMatchesByRepository
|
||||
}
|
||||
|
||||
func (v vulnerabilityMatchCountByRepositoryResolver) ID() graphql.ID {
|
||||
return resolverstubs.MarshalID("VulnerabilityMatchCountByRepository", v.v.ID)
|
||||
}
|
||||
|
||||
func (v vulnerabilityMatchCountByRepositoryResolver) RepositoryName() string {
|
||||
return v.v.RepositoryName
|
||||
}
|
||||
|
||||
func (v vulnerabilityMatchCountByRepositoryResolver) MatchCount() int32 {
|
||||
return v.v.MatchCount
|
||||
}
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/policies"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/ranking"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/reposcheduler"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/sentinel"
|
||||
codeintelshared "github.com/sourcegraph/sourcegraph/internal/codeintel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
@ -25,7 +24,6 @@ type Services struct {
|
||||
PoliciesService *policies.Service
|
||||
RankingService *ranking.Service
|
||||
UploadsService *uploads.Service
|
||||
SentinelService *sentinel.Service
|
||||
ContextService *context.Service
|
||||
GitserverClient gitserver.Client
|
||||
}
|
||||
@ -46,7 +44,6 @@ func NewServices(deps ServiceDependencies) (Services, error) {
|
||||
autoIndexingSvc := autoindexing.NewService(deps.ObservationCtx, db, dependenciesSvc, policiesSvc, gitserverClient.Scoped("autoindexing"))
|
||||
codenavSvc := codenav.NewService(deps.ObservationCtx, db, codeIntelDB, uploadsSvc, gitserverClient.Scoped("codenav"))
|
||||
rankingSvc := ranking.NewService(deps.ObservationCtx, db, codeIntelDB)
|
||||
sentinelService := sentinel.NewService(deps.ObservationCtx, db)
|
||||
contextService := context.NewService(deps.ObservationCtx, db)
|
||||
reposchedulingService := reposcheduler.NewService(reposcheduler.NewPreciseStore(deps.ObservationCtx, db))
|
||||
|
||||
@ -58,7 +55,6 @@ func NewServices(deps ServiceDependencies) (Services, error) {
|
||||
PoliciesService: policiesSvc,
|
||||
RankingService: rankingSvc,
|
||||
UploadsService: uploadsSvc,
|
||||
SentinelService: sentinelService,
|
||||
ContextService: contextService,
|
||||
GitserverClient: gitserverClient,
|
||||
}, nil
|
||||
|
||||
Loading…
Reference in New Issue
Block a user