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:
Erik Seliger 2024-04-12 07:25:40 +02:00 committed by GitHub
parent 2b53ae6b49
commit b255d93ecf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 2 additions and 4012 deletions

View File

@ -205,7 +205,6 @@ go_library(
"codeintel.graphql",
"codeintel.policies.graphql",
"codeintel.ranking.graphql",
"codeintel.sentinel.graphql",
"cody_context.graphql",
"completions.graphql",
"compute.graphql",

View File

@ -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!
}

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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",

View File

@ -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
}

View File

@ -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(),

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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",

View File

@ -9,7 +9,6 @@ go_library(
"policies.go",
"ranking.go",
"root_resolver.go",
"sentinel.go",
"uploads.go",
"utils.go",
],

View File

@ -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)
}

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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"],
)

View File

@ -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.")
}

View File

@ -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)
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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),
}
}

View File

@ -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",
],
)

View File

@ -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.")
}

View File

@ -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),
)
}

View File

@ -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,
}
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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"),
}
}

View File

@ -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),
}
}

View File

@ -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
}

View File

@ -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
}
}
}
}
}

View File

@ -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{}
}

View File

@ -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)
}

View File

@ -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__"],
)

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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"),
}
}

View File

@ -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
}

View File

@ -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