diff --git a/go.mod b/go.mod index b48044248..c39245929 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/nais/pgrator/pkg/api v0.0.0-20260219115817-cf954d58c04e github.com/nais/tester v0.1.1 github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe - github.com/nais/v13s/pkg/api v0.0.0-20260528080657-d4f49e5737da + github.com/nais/v13s/pkg/api v0.0.0-20260617075806-adadfda4fd8d github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pressly/goose/v3 v3.27.0 github.com/prometheus/client_golang v1.23.2 @@ -76,10 +76,10 @@ require ( go.opentelemetry.io/otel/trace v1.44.0 golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa golang.org/x/oauth2 v0.36.0 - golang.org/x/sync v0.20.0 + golang.org/x/sync v0.21.0 golang.org/x/text v0.37.0 golang.org/x/tools v0.45.0 - google.golang.org/api v0.280.0 + google.golang.org/api v0.284.0 google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 @@ -239,7 +239,7 @@ require ( github.com/google/flatbuffers v25.12.19+incompatible // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect github.com/googleapis/gax-go/v2 v2.22.0 // indirect github.com/gookit/color v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect diff --git a/go.sum b/go.sum index 072ae6985..3a5640eb0 100644 --- a/go.sum +++ b/go.sum @@ -520,8 +520,8 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= -github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/enterprise-certificate-proxy v0.3.16 h1:F/VPrx0YPBdksZJQdCAp0WUsqnNmZpUZszzfYt0M5Dw= +github.com/googleapis/enterprise-certificate-proxy v0.3.16/go.mod h1:9Yb0eAkH/Xqhvv3zbeKf/+wMJqCeocWc6KIhDvEAuYE= github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= @@ -813,8 +813,8 @@ github.com/nais/tester v0.1.1 h1:tpJ5HKpu3mEIWX/mec0Yj0xLHEpt+MwTAsj282n0Py0= github.com/nais/tester v0.1.1/go.mod h1:NCQMcgftHz/EXorob1XwDTOqkQmImDqr51YQ2Uea9Pc= github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe h1:CdRVopOihru4tXVwKZjhg6C8SbPLCQYOhJKpjBZYhjg= github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe/go.mod h1:Tiz/1If3WgcfvNhmsO5DiQC+L+1XhBG3KWbIfbjx4EU= -github.com/nais/v13s/pkg/api v0.0.0-20260528080657-d4f49e5737da h1:59leNz7qKRctGQS6xUnPzVUqa2NnEzVlwMDAWyhUwJs= -github.com/nais/v13s/pkg/api v0.0.0-20260528080657-d4f49e5737da/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= +github.com/nais/v13s/pkg/api v0.0.0-20260617075806-adadfda4fd8d h1:jEokr0rmq9Y4jk96QXb7lxq5qL0UdU6ZYauiyzxpVMM= +github.com/nais/v13s/pkg/api v0.0.0-20260617075806-adadfda4fd8d/go.mod h1:Ct3ihc4Qjjxt2h92Z+qttn0kkgtR8JQ7pmTF7PExH3s= github.com/ncruces/go-sqlite3 v0.32.0 h1:hNBUXp88LrfQCsuyXLqWTbTUG35sUuktDsqhhgHvU20= github.com/ncruces/go-sqlite3 v0.32.0/go.mod h1:MIWTK60ONDl0oVY073zYvJP21C3Dly6P9bxVpgkLwdQ= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= @@ -1311,8 +1311,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1387,8 +1387,8 @@ gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -google.golang.org/api v0.280.0 h1:F4OfEHZhZh6a7uTufJAXXVd/2TQ8EjM4vZH+jX/vFYk= -google.golang.org/api v0.280.0/go.mod h1:oGKmPZRDoD3vdkf6MA7F4VNkR1rxCiuaPSkhsf3EolU= +google.golang.org/api v0.284.0 h1:i+cKTgeQRcRySkP7QTl5PDO7/pAm8EcMFIUMlNbk4Vc= +google.golang.org/api v0.284.0/go.mod h1:AU44fU+XVZOCcd8uLaBIa/ZgzgPf/0qqY3+m7lQaado= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genai v1.54.0 h1:ZQCa70WMTJDI11FdqWCzGvZ5PanpcpfoO6jl/lrSnGU= diff --git a/integration_tests/issues_for_team.lua b/integration_tests/issues_for_team.lua index a90c26a46..258c39be2 100644 --- a/integration_tests/issues_for_team.lua +++ b/integration_tests/issues_for_team.lua @@ -555,8 +555,8 @@ Test.gql("VulnerableImageIssue", function(t) nodes = { { __typename = "VulnerableImageIssue", - message = "Image 'vulnerable-image' has 5 critical vulnerabilities and a risk score of 250", - severity = "WARNING", + message = "Image 'vulnerable-image' has 2 urgent vulnerabilities", + severity = "CRITICAL", critical = 5, riskScore = 250, workload = { diff --git a/integration_tests/vulnerabilities.lua b/integration_tests/vulnerabilities.lua index 2ede770b0..99c40851a 100644 --- a/integration_tests/vulnerabilities.lua +++ b/integration_tests/vulnerabilities.lua @@ -63,6 +63,19 @@ Test.gql("List vulnerability summaries for team", function(t) } vulnerabilitySummary{ total + countsBySeverity { + critical + high + medium + low + unassigned + } + countsByPriority { + urgent + highRisk + elevatedRisk + monitor + } critical high medium @@ -94,6 +107,19 @@ Test.gql("List vulnerability summaries for team", function(t) }, vulnerabilitySummary = { total = NotNull(), + countsBySeverity = { + critical = NotNull(), + high = NotNull(), + medium = NotNull(), + low = NotNull(), + unassigned = NotNull(), + }, + countsByPriority = { + urgent = NotNull(), + highRisk = NotNull(), + elevatedRisk = NotNull(), + monitor = NotNull(), + }, critical = NotNull(), high = NotNull(), medium = NotNull(), diff --git a/internal/graph/gengql/issues.generated.go b/internal/graph/gengql/issues.generated.go index fb56b878a..505bc749b 100644 --- a/internal/graph/gengql/issues.generated.go +++ b/internal/graph/gengql/issues.generated.go @@ -50,6 +50,11 @@ type ExternalIngressCriticalVulnerabilityIssueResolver interface { Workload(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (workload.Workload, error) } +type ExternalIngressUrgentVulnerabilityIssueResolver interface { + TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (*team.TeamEnvironment, error) + + Workload(ctx context.Context, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (workload.Workload, error) +} type FailedSynchronizationIssueResolver interface { TeamEnvironment(ctx context.Context, obj *issue.FailedSynchronizationIssue) (*team.TeamEnvironment, error) @@ -796,6 +801,185 @@ func (ec *executionContext) fieldContext_ExternalIngressCriticalVulnerabilityIss return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) } +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue_id(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressUrgentVulnerabilityIssue_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ID, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v ident.Ident) graphql.Marshaler { + return ec.marshalNID2githubᚗcomᚋnaisᚋapiᚋinternalᚋgraphᚋidentᚐIdent(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ExternalIngressUrgentVulnerabilityIssue_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressUrgentVulnerabilityIssue", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue_teamEnvironment(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressUrgentVulnerabilityIssue_teamEnvironment(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.ExternalIngressUrgentVulnerabilityIssue().TeamEnvironment(ctx, obj) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *team.TeamEnvironment) graphql.Marshaler { + return ec.marshalNTeamEnvironment2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋteamᚐTeamEnvironment(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ExternalIngressUrgentVulnerabilityIssue_teamEnvironment(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ExternalIngressUrgentVulnerabilityIssue", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_TeamEnvironment(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue_severity(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressUrgentVulnerabilityIssue_severity(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Severity, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v issue.Severity) graphql.Marshaler { + return ec.marshalNSeverity2githubᚗcomᚋnaisᚋapiᚋinternalᚋissueᚐSeverity(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ExternalIngressUrgentVulnerabilityIssue_severity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressUrgentVulnerabilityIssue", field, false, false, errors.New("field of type Severity does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue_message(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressUrgentVulnerabilityIssue_message(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Message, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ExternalIngressUrgentVulnerabilityIssue_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressUrgentVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue_workload(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressUrgentVulnerabilityIssue_workload(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.ExternalIngressUrgentVulnerabilityIssue().Workload(ctx, obj) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v workload.Workload) graphql.Marshaler { + return ec.marshalNWorkload2githubᚗcomᚋnaisᚋapiᚋinternalᚋworkloadᚐWorkload(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ExternalIngressUrgentVulnerabilityIssue_workload(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ExternalIngressUrgentVulnerabilityIssue", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("FieldContext.Child cannot be called on type INTERFACE") + }, + } + return fc, nil +} + +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue_priorityUrgent(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressUrgentVulnerabilityIssue_priorityUrgent(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.PriorityUrgent, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ExternalIngressUrgentVulnerabilityIssue_priorityUrgent(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressUrgentVulnerabilityIssue", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue_ingresses(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressUrgentVulnerabilityIssue_ingresses(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Ingresses, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v []string) graphql.Marshaler { + return ec.marshalNString2ᚕstringᚄ(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ExternalIngressUrgentVulnerabilityIssue_ingresses(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressUrgentVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) +} + func (ec *executionContext) _FailedSynchronizationIssue_id(ctx context.Context, field graphql.CollectedField, obj *issue.FailedSynchronizationIssue) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -3322,6 +3506,13 @@ func (ec *executionContext) _Issue(ctx context.Context, sel ast.SelectionSet, ob return graphql.Null } return ec._FailedSynchronizationIssue(ctx, sel, obj) + case issue.ExternalIngressUrgentVulnerabilityIssue: + return ec._ExternalIngressUrgentVulnerabilityIssue(ctx, sel, &obj) + case *issue.ExternalIngressUrgentVulnerabilityIssue: + if obj == nil { + return graphql.Null + } + return ec._ExternalIngressUrgentVulnerabilityIssue(ctx, sel, obj) case issue.ExternalIngressCriticalVulnerabilityIssue: return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, &obj) case *issue.ExternalIngressCriticalVulnerabilityIssue: @@ -3877,6 +4068,137 @@ func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue(ctx conte return out } +var externalIngressUrgentVulnerabilityIssueImplementors = []string{"ExternalIngressUrgentVulnerabilityIssue", "Issue", "Node"} + +func (ec *executionContext) _ExternalIngressUrgentVulnerabilityIssue(ctx context.Context, sel ast.SelectionSet, obj *issue.ExternalIngressUrgentVulnerabilityIssue) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, externalIngressUrgentVulnerabilityIssueImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ExternalIngressUrgentVulnerabilityIssue") + case "id": + out.Values[i] = ec._ExternalIngressUrgentVulnerabilityIssue_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "teamEnvironment": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._ExternalIngressUrgentVulnerabilityIssue_teamEnvironment(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "severity": + out.Values[i] = ec._ExternalIngressUrgentVulnerabilityIssue_severity(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "message": + out.Values[i] = ec._ExternalIngressUrgentVulnerabilityIssue_message(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "workload": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._ExternalIngressUrgentVulnerabilityIssue_workload(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "priorityUrgent": + out.Values[i] = ec._ExternalIngressUrgentVulnerabilityIssue_priorityUrgent(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "ingresses": + out.Values[i] = ec._ExternalIngressUrgentVulnerabilityIssue_ingresses(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var failedSynchronizationIssueImplementors = []string{"FailedSynchronizationIssue", "Issue", "Node"} func (ec *executionContext) _FailedSynchronizationIssue(ctx context.Context, sel ast.SelectionSet, obj *issue.FailedSynchronizationIssue) graphql.Marshaler { diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 21154d182..49c4f4d47 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -83,6 +83,7 @@ type ResolverRoot interface { DeprecatedRegistryIssue() DeprecatedRegistryIssueResolver Environment() EnvironmentResolver ExternalIngressCriticalVulnerabilityIssue() ExternalIngressCriticalVulnerabilityIssueResolver + ExternalIngressUrgentVulnerabilityIssue() ExternalIngressUrgentVulnerabilityIssueResolver FailedSynchronizationIssue() FailedSynchronizationIssueResolver Ingress() IngressResolver InstanceGroup() InstanceGroupResolver @@ -533,14 +534,19 @@ type ComplexityRoot struct { } CVE struct { - CVSSScore func(childComplexity int) int - Description func(childComplexity int) int - DetailsLink func(childComplexity int) int - ID func(childComplexity int) int - Identifier func(childComplexity int) int - Severity func(childComplexity int) int - Title func(childComplexity int) int - Workloads func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, filter *vulnerability.CVEWorkloadsFilter) int + CVSSScore func(childComplexity int) int + Description func(childComplexity int) int + DetailsLink func(childComplexity int) int + EpssPercentile func(childComplexity int) int + EpssScore func(childComplexity int) int + HasKevEntry func(childComplexity int) int + ID func(childComplexity int) int + Identifier func(childComplexity int) int + KnownRansomwareUse func(childComplexity int) int + Priority func(childComplexity int) int + Severity func(childComplexity int) int + Title func(childComplexity int) int + Workloads func(childComplexity int, first *int, after *pagination.Cursor, last *int, before *pagination.Cursor, filter *vulnerability.CVEWorkloadsFilter) int } CVEConnection struct { @@ -956,6 +962,16 @@ type ComplexityRoot struct { Workload func(childComplexity int) int } + ExternalIngressUrgentVulnerabilityIssue struct { + ID func(childComplexity int) int + Ingresses func(childComplexity int) int + Message func(childComplexity int) int + PriorityUrgent func(childComplexity int) int + Severity func(childComplexity int) int + TeamEnvironment func(childComplexity int) int + Workload func(childComplexity int) int + } + ExternalNetworkPolicyHost struct { Ports func(childComplexity int) int Target func(childComplexity int) int @@ -1045,8 +1061,13 @@ type ComplexityRoot struct { ImageVulnerability struct { CvssScore func(childComplexity int) int Description func(childComplexity int) int + EpssPercentile func(childComplexity int) int + EpssScore func(childComplexity int) int + FixVersion func(childComplexity int) int + HasKevEntry func(childComplexity int) int ID func(childComplexity int) int Identifier func(childComplexity int) int + KnownRansomwareUse func(childComplexity int) int Package func(childComplexity int) int Severity func(childComplexity int) int SeveritySince func(childComplexity int) int @@ -1075,15 +1096,32 @@ type ComplexityRoot struct { } ImageVulnerabilitySummary struct { - Critical func(childComplexity int) int - High func(childComplexity int) int - LastUpdated func(childComplexity int) int - Low func(childComplexity int) int - Medium func(childComplexity int) int - RiskScore func(childComplexity int) int - StaleImageTag func(childComplexity int) int - Total func(childComplexity int) int - Unassigned func(childComplexity int) int + CountsByPriority func(childComplexity int) int + CountsBySeverity func(childComplexity int) int + Critical func(childComplexity int) int + High func(childComplexity int) int + LastUpdated func(childComplexity int) int + Low func(childComplexity int) int + Medium func(childComplexity int) int + RiskScore func(childComplexity int) int + StaleImageTag func(childComplexity int) int + Total func(childComplexity int) int + Unassigned func(childComplexity int) int + } + + ImageVulnerabilitySummaryCountsByPriority struct { + ElevatedRisk func(childComplexity int) int + HighRisk func(childComplexity int) int + Monitor func(childComplexity int) int + Urgent func(childComplexity int) int + } + + ImageVulnerabilitySummaryCountsBySeverity struct { + Critical func(childComplexity int) int + High func(childComplexity int) int + Low func(childComplexity int) int + Medium func(childComplexity int) int + Unassigned func(childComplexity int) int } ImageVulnerabilitySuppression struct { @@ -5282,6 +5320,27 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.CVE.DetailsLink(childComplexity), true + case "CVE.epssPercentile": + if e.ComplexityRoot.CVE.EpssPercentile == nil { + break + } + + return e.ComplexityRoot.CVE.EpssPercentile(childComplexity), true + + case "CVE.epssScore": + if e.ComplexityRoot.CVE.EpssScore == nil { + break + } + + return e.ComplexityRoot.CVE.EpssScore(childComplexity), true + + case "CVE.hasKevEntry": + if e.ComplexityRoot.CVE.HasKevEntry == nil { + break + } + + return e.ComplexityRoot.CVE.HasKevEntry(childComplexity), true + case "CVE.id": if e.ComplexityRoot.CVE.ID == nil { break @@ -5296,6 +5355,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.CVE.Identifier(childComplexity), true + case "CVE.knownRansomwareUse": + if e.ComplexityRoot.CVE.KnownRansomwareUse == nil { + break + } + + return e.ComplexityRoot.CVE.KnownRansomwareUse(childComplexity), true + + case "CVE.priority": + if e.ComplexityRoot.CVE.Priority == nil { + break + } + + return e.ComplexityRoot.CVE.Priority(childComplexity), true + case "CVE.severity": if e.ComplexityRoot.CVE.Severity == nil { break @@ -6840,6 +6913,55 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Workload(childComplexity), true + case "ExternalIngressUrgentVulnerabilityIssue.id": + if e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.ID == nil { + break + } + + return e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.ID(childComplexity), true + + case "ExternalIngressUrgentVulnerabilityIssue.ingresses": + if e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Ingresses == nil { + break + } + + return e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Ingresses(childComplexity), true + + case "ExternalIngressUrgentVulnerabilityIssue.message": + if e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Message == nil { + break + } + + return e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Message(childComplexity), true + + case "ExternalIngressUrgentVulnerabilityIssue.priorityUrgent": + if e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.PriorityUrgent == nil { + break + } + + return e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.PriorityUrgent(childComplexity), true + + case "ExternalIngressUrgentVulnerabilityIssue.severity": + if e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Severity == nil { + break + } + + return e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Severity(childComplexity), true + + case "ExternalIngressUrgentVulnerabilityIssue.teamEnvironment": + if e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.TeamEnvironment == nil { + break + } + + return e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.TeamEnvironment(childComplexity), true + + case "ExternalIngressUrgentVulnerabilityIssue.workload": + if e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Workload == nil { + break + } + + return e.ComplexityRoot.ExternalIngressUrgentVulnerabilityIssue.Workload(childComplexity), true + case "ExternalNetworkPolicyHost.ports": if e.ComplexityRoot.ExternalNetworkPolicyHost.Ports == nil { break @@ -7183,6 +7305,34 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerability.Description(childComplexity), true + case "ImageVulnerability.epssPercentile": + if e.ComplexityRoot.ImageVulnerability.EpssPercentile == nil { + break + } + + return e.ComplexityRoot.ImageVulnerability.EpssPercentile(childComplexity), true + + case "ImageVulnerability.epssScore": + if e.ComplexityRoot.ImageVulnerability.EpssScore == nil { + break + } + + return e.ComplexityRoot.ImageVulnerability.EpssScore(childComplexity), true + + case "ImageVulnerability.fixVersion": + if e.ComplexityRoot.ImageVulnerability.FixVersion == nil { + break + } + + return e.ComplexityRoot.ImageVulnerability.FixVersion(childComplexity), true + + case "ImageVulnerability.hasKevEntry": + if e.ComplexityRoot.ImageVulnerability.HasKevEntry == nil { + break + } + + return e.ComplexityRoot.ImageVulnerability.HasKevEntry(childComplexity), true + case "ImageVulnerability.id": if e.ComplexityRoot.ImageVulnerability.ID == nil { break @@ -7197,6 +7347,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerability.Identifier(childComplexity), true + case "ImageVulnerability.knownRansomwareUse": + if e.ComplexityRoot.ImageVulnerability.KnownRansomwareUse == nil { + break + } + + return e.ComplexityRoot.ImageVulnerability.KnownRansomwareUse(childComplexity), true + case "ImageVulnerability.package": if e.ComplexityRoot.ImageVulnerability.Package == nil { break @@ -7288,6 +7445,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySample.Summary(childComplexity), true + case "ImageVulnerabilitySummary.countsByPriority": + if e.ComplexityRoot.ImageVulnerabilitySummary.CountsByPriority == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.CountsByPriority(childComplexity), true + + case "ImageVulnerabilitySummary.countsBySeverity": + if e.ComplexityRoot.ImageVulnerabilitySummary.CountsBySeverity == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.CountsBySeverity(childComplexity), true + case "ImageVulnerabilitySummary.critical": if e.ComplexityRoot.ImageVulnerabilitySummary.Critical == nil { break @@ -7351,6 +7522,69 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.Unassigned(childComplexity), true + case "ImageVulnerabilitySummaryCountsByPriority.elevatedRisk": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.ElevatedRisk == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.ElevatedRisk(childComplexity), true + + case "ImageVulnerabilitySummaryCountsByPriority.highRisk": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.HighRisk == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.HighRisk(childComplexity), true + + case "ImageVulnerabilitySummaryCountsByPriority.monitor": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.Monitor == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.Monitor(childComplexity), true + + case "ImageVulnerabilitySummaryCountsByPriority.urgent": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.Urgent == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsByPriority.Urgent(childComplexity), true + + case "ImageVulnerabilitySummaryCountsBySeverity.critical": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Critical == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Critical(childComplexity), true + + case "ImageVulnerabilitySummaryCountsBySeverity.high": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.High == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.High(childComplexity), true + + case "ImageVulnerabilitySummaryCountsBySeverity.low": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Low == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Low(childComplexity), true + + case "ImageVulnerabilitySummaryCountsBySeverity.medium": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Medium == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Medium(childComplexity), true + + case "ImageVulnerabilitySummaryCountsBySeverity.unassigned": + if e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Unassigned == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummaryCountsBySeverity.Unassigned(childComplexity), true + case "ImageVulnerabilitySuppression.reason": if e.ComplexityRoot.ImageVulnerabilitySuppression.Reason == nil { break @@ -23454,6 +23688,8 @@ enum IssueType { MISSING_SBOM VULNERABLE_IMAGE EXTERNAL_INGRESS_CRITICAL_VULNERABILITY + @deprecated(reason: "Use EXTERNAL_INGRESS_URGENT_VULNERABILITY.") + EXTERNAL_INGRESS_URGENT_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." APPLICATION_RESTART_LOOP @@ -23470,14 +23706,35 @@ type VulnerableImageIssue implements Issue & Node { critical: Int! } +"Deprecated: use ExternalIngressUrgentVulnerabilityIssue." type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { + id: ID! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + teamEnvironment: TeamEnvironment! + @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + severity: Severity! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + message: String! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + + workload: Workload! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + cvssScore: Float! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + ingresses: [String!]! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") +} + +"Raised when a workload with external ingresses has one or more urgent vulnerability-priority findings." +type ExternalIngressUrgentVulnerabilityIssue implements Issue & Node { + "The globally unique identifier for this issue." id: ID! + "The team environment where the affected workload is deployed." teamEnvironment: TeamEnvironment! + "The severity assigned to this issue." severity: Severity! + "A human-readable description of the issue." message: String! + "The workload with urgent vulnerabilities and external ingresses." workload: Workload! - cvssScore: Float! + "Number of urgent vulnerabilities on the workload." + priorityUrgent: Int! + "External ingress URLs that expose the workload." ingresses: [String!]! } @@ -31499,10 +31756,16 @@ input CVEOrder { } enum CVEOrderField { + "Order by CVE identifier." IDENTIFIER + "Order by CVE severity." SEVERITY + "Order by CVSS score." CVSS_SCORE + "Order by number of affected workloads." AFFECTED_WORKLOADS_COUNT + "Order by CVE priority derived from threat intelligence signals." + PRIORITY } extend interface Workload { @@ -31696,6 +31959,18 @@ input TeamVulnerabilitySummaryFilter { Only return vulnerability summaries for the given environment. """ environmentName: String + + """ + Deprecated: use environmentName instead. + Only one environment is supported if this list is used. + """ + environments: [String!] + @deprecated(reason: "Use environmentName instead. Only one value is supported.") + + """ + Only return vulnerability summaries at or above the given vulnerability priority. + """ + priority: CVEPriority } """ @@ -31716,20 +31991,26 @@ type ImageVulnerabilitySummary { "Risk score of the image." riskScore: Int! + "Vulnerability counts grouped by severity." + countsBySeverity: ImageVulnerabilitySummaryCountsBySeverity! + + "Vulnerability counts grouped by operational priority." + countsByPriority: ImageVulnerabilitySummaryCountsByPriority! + "Number of vulnerabilities with severity LOW." - low: Int! + low: Int! @deprecated(reason: "Use countsBySeverity.low instead.") "Number of vulnerabilities with severity MEDIUM." - medium: Int! + medium: Int! @deprecated(reason: "Use countsBySeverity.medium instead.") "Number of vulnerabilities with severity HIGH." - high: Int! + high: Int! @deprecated(reason: "Use countsBySeverity.high instead.") "Number of vulnerabilities with severity CRITICAL." - critical: Int! + critical: Int! @deprecated(reason: "Use countsBySeverity.critical instead.") "Number of vulnerabilities with severity UNASSIGNED." - unassigned: Int! + unassigned: Int! @deprecated(reason: "Use countsBySeverity.unassigned instead.") "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -31742,6 +32023,37 @@ type ImageVulnerabilitySummary { staleImageTag: String } +type ImageVulnerabilitySummaryCountsBySeverity { + "Number of vulnerabilities with severity CRITICAL." + critical: Int! + + "Number of vulnerabilities with severity HIGH." + high: Int! + + "Number of vulnerabilities with severity MEDIUM." + medium: Int! + + "Number of vulnerabilities with severity LOW." + low: Int! + + "Number of vulnerabilities with severity UNASSIGNED." + unassigned: Int! +} + +type ImageVulnerabilitySummaryCountsByPriority { + "Known-exploited vulnerabilities that require immediate action." + urgent: Int! + + "Vulnerabilities with strong exploitation indicators." + highRisk: Int! + + "Vulnerabilities with elevated exploitation risk." + elevatedRisk: Int! + + "Vulnerabilities that should be monitored." + monitor: Int! +} + type ImageVulnerabilityConnection { "Information to aid in pagination." pageInfo: PageInfo! @@ -31809,6 +32121,9 @@ type ImageVulnerability implements Node { "Package name of the vulnerability." package: String! + "First known package version that contains a fix." + fixVersion: String + suppression: ImageVulnerabilitySuppression "Timestamp of when the vulnerability got its current severity." @@ -31819,6 +32134,29 @@ type ImageVulnerability implements Node { "CVSS score of the vulnerability." cvssScore: Float + + "EPSS score of the vulnerability." + epssScore: Float + + "EPSS percentile of the vulnerability (0-1)." + epssPercentile: Float + + "Whether the vulnerability has a CISA KEV entry." + hasKevEntry: Boolean! + + "Whether the vulnerability has known ransomware use." + knownRansomwareUse: Boolean! +} + +enum CVEPriority { + "Vulnerability is known to be actively exploited and requires immediate action." + ACT_NOW + "Vulnerability is associated with ransomware or has a high EPSS percentile." + HIGH + "Vulnerability has a critical or high severity and elevated EPSS percentile." + ELEVATED + "Vulnerability requires monitoring but no immediate action." + MONITOR } type CVE implements Node { @@ -31843,6 +32181,21 @@ type CVE implements Node { "CVSS score of the CVE." cvssScore: Float + "Priority of the CVE based on threat intelligence signals." + priority: CVEPriority! + + "EPSS score of the CVE (probability of exploitation)." + epssScore: Float + + "EPSS percentile of the CVE." + epssPercentile: Float + + "Whether the CVE has a Known Exploited Vulnerability (KEV) entry." + hasKevEntry: Boolean! + + "Whether the CVE is known to be used in ransomware attacks." + knownRansomwareUse: Boolean! + "Affected workloads" workloads( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -31947,12 +32300,20 @@ input ImageVulnerabilityOrder { } enum ImageVulnerabilityOrderField { + "Order by vulnerability identifier." IDENTIFIER + "Order by vulnerability severity." SEVERITY + "Order by when the vulnerability received its current severity." SEVERITY_SINCE + "Order by affected package name." PACKAGE + "Order by suppression state." STATE + "Order by whether the vulnerability is suppressed." SUPPRESSED + "Order by vulnerability priority derived from threat intelligence signals." + PRIORITY } type WorkloadVulnerabilitySummary implements Node { @@ -31995,29 +32356,57 @@ enum VulnerabilitySummaryOrderByField { """ ENVIRONMENT """ - Order by risk score" + Order by risk score. """ VULNERABILITY_RISK_SCORE """ - Order by vulnerability severity critical" + Order by vulnerability severity critical. """ VULNERABILITY_SEVERITY_CRITICAL """ - Order by vulnerability severity high" + Order by vulnerability severity high. """ VULNERABILITY_SEVERITY_HIGH """ - Order by vulnerability severity medium" + Order by vulnerability severity medium. """ VULNERABILITY_SEVERITY_MEDIUM """ - Order by vulnerability severity low" + Order by vulnerability severity low. """ VULNERABILITY_SEVERITY_LOW """ - Order by vulnerability severity unassigned" + Order by vulnerability severity unassigned. """ VULNERABILITY_SEVERITY_UNASSIGNED + """ + Order by the number of urgent vulnerabilities. + + Urgent vulnerabilities are known to be actively exploited and should be + prioritized for immediate action. + """ + VULNERABILITY_PRIORITY_URGENT + """ + Order by the number of high-risk vulnerabilities. + + High-risk vulnerabilities are not known exploited, but have strong + exploitation indicators such as known ransomware use or very high EPSS. + """ + VULNERABILITY_PRIORITY_HIGH_RISK + """ + Order by the number of elevated-risk vulnerabilities. + + Elevated-risk vulnerabilities are lower priority than high-risk, but still + have meaningful exploitation risk signals. + """ + VULNERABILITY_PRIORITY_ELEVATED_RISK + """ + Order by the number of monitor vulnerabilities. + + Monitor vulnerabilities should be tracked, but do not currently indicate the + same operational urgency as the higher priority buckets. + """ + VULNERABILITY_PRIORITY_MONITOR } type TenantVulnerabilitySummary { @@ -33337,6 +33726,16 @@ func (ec *executionContext) childFields_CVE(ctx context.Context, field graphql.C return ec.fieldContext_CVE_detailsLink(ctx, field) case "cvssScore": return ec.fieldContext_CVE_cvssScore(ctx, field) + case "priority": + return ec.fieldContext_CVE_priority(ctx, field) + case "epssScore": + return ec.fieldContext_CVE_epssScore(ctx, field) + case "epssPercentile": + return ec.fieldContext_CVE_epssPercentile(ctx, field) + case "hasKevEntry": + return ec.fieldContext_CVE_hasKevEntry(ctx, field) + case "knownRansomwareUse": + return ec.fieldContext_CVE_knownRansomwareUse(ctx, field) case "workloads": return ec.fieldContext_CVE_workloads(ctx, field) } @@ -34083,6 +34482,8 @@ func (ec *executionContext) childFields_ImageVulnerability(ctx context.Context, return ec.fieldContext_ImageVulnerability_description(ctx, field) case "package": return ec.fieldContext_ImageVulnerability_package(ctx, field) + case "fixVersion": + return ec.fieldContext_ImageVulnerability_fixVersion(ctx, field) case "suppression": return ec.fieldContext_ImageVulnerability_suppression(ctx, field) case "severitySince": @@ -34091,6 +34492,14 @@ func (ec *executionContext) childFields_ImageVulnerability(ctx context.Context, return ec.fieldContext_ImageVulnerability_vulnerabilityDetailsLink(ctx, field) case "cvssScore": return ec.fieldContext_ImageVulnerability_cvssScore(ctx, field) + case "epssScore": + return ec.fieldContext_ImageVulnerability_epssScore(ctx, field) + case "epssPercentile": + return ec.fieldContext_ImageVulnerability_epssPercentile(ctx, field) + case "hasKevEntry": + return ec.fieldContext_ImageVulnerability_hasKevEntry(ctx, field) + case "knownRansomwareUse": + return ec.fieldContext_ImageVulnerability_knownRansomwareUse(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageVulnerability", field.Name) } @@ -34141,6 +34550,10 @@ func (ec *executionContext) childFields_ImageVulnerabilitySummary(ctx context.Co return ec.fieldContext_ImageVulnerabilitySummary_total(ctx, field) case "riskScore": return ec.fieldContext_ImageVulnerabilitySummary_riskScore(ctx, field) + case "countsBySeverity": + return ec.fieldContext_ImageVulnerabilitySummary_countsBySeverity(ctx, field) + case "countsByPriority": + return ec.fieldContext_ImageVulnerabilitySummary_countsByPriority(ctx, field) case "low": return ec.fieldContext_ImageVulnerabilitySummary_low(ctx, field) case "medium": @@ -34159,6 +34572,36 @@ func (ec *executionContext) childFields_ImageVulnerabilitySummary(ctx context.Co return nil, fmt.Errorf("no field named %q was found under type ImageVulnerabilitySummary", field.Name) } +func (ec *executionContext) childFields_ImageVulnerabilitySummaryCountsByPriority(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "urgent": + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_urgent(ctx, field) + case "highRisk": + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_highRisk(ctx, field) + case "elevatedRisk": + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_elevatedRisk(ctx, field) + case "monitor": + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_monitor(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ImageVulnerabilitySummaryCountsByPriority", field.Name) +} + +func (ec *executionContext) childFields_ImageVulnerabilitySummaryCountsBySeverity(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "critical": + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_critical(ctx, field) + case "high": + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_high(ctx, field) + case "medium": + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_medium(ctx, field) + case "low": + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_low(ctx, field) + case "unassigned": + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_unassigned(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ImageVulnerabilitySummaryCountsBySeverity", field.Name) +} + func (ec *executionContext) childFields_ImageVulnerabilitySuppression(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "state": diff --git a/internal/graph/gengql/schema.generated.go b/internal/graph/gengql/schema.generated.go index 021c55c7a..d537b574c 100644 --- a/internal/graph/gengql/schema.generated.go +++ b/internal/graph/gengql/schema.generated.go @@ -6614,6 +6614,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._FailedSynchronizationIssue(ctx, sel, obj) + case issue.ExternalIngressUrgentVulnerabilityIssue: + return ec._ExternalIngressUrgentVulnerabilityIssue(ctx, sel, &obj) + case *issue.ExternalIngressUrgentVulnerabilityIssue: + if obj == nil { + return graphql.Null + } + return ec._ExternalIngressUrgentVulnerabilityIssue(ctx, sel, obj) case issue.ExternalIngressCriticalVulnerabilityIssue: return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, &obj) case *issue.ExternalIngressCriticalVulnerabilityIssue: diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index f0d454212..cca6d7c06 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -258,6 +258,121 @@ func (ec *executionContext) fieldContext_CVE_cvssScore(_ context.Context, field return graphql.NewScalarFieldContext("CVE", field, false, false, errors.New("field of type Float does not have child fields")) } +func (ec *executionContext) _CVE_priority(ctx context.Context, field graphql.CollectedField, obj *vulnerability.CVE) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_CVE_priority(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Priority, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v vulnerability.CVEPriority) graphql.Marshaler { + return ec.marshalNCVEPriority2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_CVE_priority(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("CVE", field, false, false, errors.New("field of type CVEPriority does not have child fields")) +} + +func (ec *executionContext) _CVE_epssScore(ctx context.Context, field graphql.CollectedField, obj *vulnerability.CVE) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_CVE_epssScore(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.EpssScore, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *float64) graphql.Marshaler { + return ec.marshalOFloat2ᚖfloat64(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_CVE_epssScore(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("CVE", field, false, false, errors.New("field of type Float does not have child fields")) +} + +func (ec *executionContext) _CVE_epssPercentile(ctx context.Context, field graphql.CollectedField, obj *vulnerability.CVE) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_CVE_epssPercentile(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.EpssPercentile, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *float64) graphql.Marshaler { + return ec.marshalOFloat2ᚖfloat64(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_CVE_epssPercentile(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("CVE", field, false, false, errors.New("field of type Float does not have child fields")) +} + +func (ec *executionContext) _CVE_hasKevEntry(ctx context.Context, field graphql.CollectedField, obj *vulnerability.CVE) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_CVE_hasKevEntry(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.HasKevEntry, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_CVE_hasKevEntry(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("CVE", field, false, false, errors.New("field of type Boolean does not have child fields")) +} + +func (ec *executionContext) _CVE_knownRansomwareUse(ctx context.Context, field graphql.CollectedField, obj *vulnerability.CVE) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_CVE_knownRansomwareUse(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.KnownRansomwareUse, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_CVE_knownRansomwareUse(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("CVE", field, false, false, errors.New("field of type Boolean does not have child fields")) +} + func (ec *executionContext) _CVE_workloads(ctx context.Context, field graphql.CollectedField, obj *vulnerability.CVE) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -820,6 +935,29 @@ func (ec *executionContext) fieldContext_ImageVulnerability_package(_ context.Co return graphql.NewScalarFieldContext("ImageVulnerability", field, false, false, errors.New("field of type String does not have child fields")) } +func (ec *executionContext) _ImageVulnerability_fixVersion(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerability) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerability_fixVersion(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.FixVersion, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { + return ec.marshalOString2ᚖstring(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerability_fixVersion(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerability", field, false, false, errors.New("field of type String does not have child fields")) +} + func (ec *executionContext) _ImageVulnerability_suppression(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerability) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -921,6 +1059,98 @@ func (ec *executionContext) fieldContext_ImageVulnerability_cvssScore(_ context. return graphql.NewScalarFieldContext("ImageVulnerability", field, false, false, errors.New("field of type Float does not have child fields")) } +func (ec *executionContext) _ImageVulnerability_epssScore(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerability) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerability_epssScore(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.EpssScore, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *float64) graphql.Marshaler { + return ec.marshalOFloat2ᚖfloat64(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerability_epssScore(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerability", field, false, false, errors.New("field of type Float does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerability_epssPercentile(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerability) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerability_epssPercentile(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.EpssPercentile, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *float64) graphql.Marshaler { + return ec.marshalOFloat2ᚖfloat64(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerability_epssPercentile(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerability", field, false, false, errors.New("field of type Float does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerability_hasKevEntry(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerability) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerability_hasKevEntry(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.HasKevEntry, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerability_hasKevEntry(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerability", field, false, false, errors.New("field of type Boolean does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerability_knownRansomwareUse(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerability) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerability_knownRansomwareUse(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.KnownRansomwareUse, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerability_knownRansomwareUse(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerability", field, false, false, errors.New("field of type Boolean does not have child fields")) +} + func (ec *executionContext) _ImageVulnerabilityConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *pagination.Connection[*vulnerability.ImageVulnerability]) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1100,75 +1330,346 @@ func (ec *executionContext) fieldContext_ImageVulnerabilityHistory_samples(_ con Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { return ec.childFields_ImageVulnerabilitySample(ctx, field) }, - } - return fc, nil + } + return fc, nil +} + +func (ec *executionContext) _ImageVulnerabilitySample_summary(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySample) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySample_summary(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Summary, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *vulnerability.ImageVulnerabilitySummary) graphql.Marshaler { + return ec.marshalNImageVulnerabilitySummary2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐImageVulnerabilitySummary(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySample_summary(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageVulnerabilitySample", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_ImageVulnerabilitySummary(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageVulnerabilitySample_date(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySample) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySample_date(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Date, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v time.Time) graphql.Marshaler { + return ec.marshalNTime2timeᚐTime(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySample_date(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySample", field, false, false, errors.New("field of type Time does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerabilitySummary_total(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_total(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Total, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_total(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerabilitySummary_riskScore(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_riskScore(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.RiskScore, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_riskScore(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerabilitySummary_countsBySeverity(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_countsBySeverity(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.CountsBySeverity, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v vulnerability.ImageVulnerabilitySummaryCountsBySeverity) graphql.Marshaler { + return ec.marshalNImageVulnerabilitySummaryCountsBySeverity2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐImageVulnerabilitySummaryCountsBySeverity(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_countsBySeverity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageVulnerabilitySummary", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_ImageVulnerabilitySummaryCountsBySeverity(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageVulnerabilitySummary_countsByPriority(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_countsByPriority(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.CountsByPriority, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v vulnerability.ImageVulnerabilitySummaryCountsByPriority) graphql.Marshaler { + return ec.marshalNImageVulnerabilitySummaryCountsByPriority2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐImageVulnerabilitySummaryCountsByPriority(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_countsByPriority(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageVulnerabilitySummary", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_ImageVulnerabilitySummaryCountsByPriority(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageVulnerabilitySummary_low(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_low(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Low, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_low(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerabilitySummary_medium(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_medium(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Medium, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_medium(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerabilitySummary_high(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_high(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.High, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_high(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerabilitySummary_critical(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_critical(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Critical, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_critical(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ImageVulnerabilitySummary_unassigned(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ImageVulnerabilitySummary_unassigned(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Unassigned, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_unassigned(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySample_summary(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySample) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummary_lastUpdated(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySample_summary(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummary_lastUpdated(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Summary, nil + return obj.LastUpdated, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *vulnerability.ImageVulnerabilitySummary) graphql.Marshaler { - return ec.marshalNImageVulnerabilitySummary2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐImageVulnerabilitySummary(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v *time.Time) graphql.Marshaler { + return ec.marshalOTime2ᚖtimeᚐTime(ctx, selections, v) }, true, - true, + false, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySample_summary(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageVulnerabilitySample", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_ImageVulnerabilitySummary(ctx, field) - }, - } - return fc, nil +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_lastUpdated(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Time does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySample_date(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySample) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummary_staleImageTag(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySample_date(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummary_staleImageTag(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Date, nil + return obj.StaleImageTag, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v time.Time) graphql.Marshaler { - return ec.marshalNTime2timeᚐTime(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { + return ec.marshalOString2ᚖstring(ctx, selections, v) }, true, - true, + false, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySample_date(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySample", field, false, false, errors.New("field of type Time does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_staleImageTag(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type String does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_total(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsByPriority_urgent(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsByPriority) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_total(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_urgent(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Total, nil + return obj.Urgent, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -1178,20 +1679,20 @@ func (ec *executionContext) _ImageVulnerabilitySummary_total(ctx context.Context true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_total(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsByPriority_urgent(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsByPriority", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_riskScore(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsByPriority_highRisk(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsByPriority) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_riskScore(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_highRisk(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.RiskScore, nil + return obj.HighRisk, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -1201,20 +1702,20 @@ func (ec *executionContext) _ImageVulnerabilitySummary_riskScore(ctx context.Con true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_riskScore(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsByPriority_highRisk(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsByPriority", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_low(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsByPriority_elevatedRisk(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsByPriority) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_low(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_elevatedRisk(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Low, nil + return obj.ElevatedRisk, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -1224,20 +1725,20 @@ func (ec *executionContext) _ImageVulnerabilitySummary_low(ctx context.Context, true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_low(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsByPriority_elevatedRisk(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsByPriority", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_medium(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsByPriority_monitor(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsByPriority) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_medium(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsByPriority_monitor(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Medium, nil + return obj.Monitor, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -1247,20 +1748,20 @@ func (ec *executionContext) _ImageVulnerabilitySummary_medium(ctx context.Contex true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_medium(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsByPriority_monitor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsByPriority", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_high(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsBySeverity_critical(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsBySeverity) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_high(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_critical(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.High, nil + return obj.Critical, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -1270,20 +1771,20 @@ func (ec *executionContext) _ImageVulnerabilitySummary_high(ctx context.Context, true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_high(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsBySeverity_critical(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsBySeverity", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_critical(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsBySeverity_high(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsBySeverity) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_critical(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_high(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Critical, nil + return obj.High, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -1293,20 +1794,20 @@ func (ec *executionContext) _ImageVulnerabilitySummary_critical(ctx context.Cont true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_critical(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsBySeverity_high(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsBySeverity", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_unassigned(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsBySeverity_medium(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsBySeverity) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_unassigned(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_medium(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Unassigned, nil + return obj.Medium, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -1316,54 +1817,54 @@ func (ec *executionContext) _ImageVulnerabilitySummary_unassigned(ctx context.Co true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_unassigned(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsBySeverity_medium(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsBySeverity", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_lastUpdated(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsBySeverity_low(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsBySeverity) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_lastUpdated(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_low(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.LastUpdated, nil + return obj.Low, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *time.Time) graphql.Marshaler { - return ec.marshalOTime2ᚖtimeᚐTime(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) }, true, - false, + true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_lastUpdated(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Time does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsBySeverity_low(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsBySeverity", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_staleImageTag(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummaryCountsBySeverity_unassigned(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummaryCountsBySeverity) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ImageVulnerabilitySummary_staleImageTag(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummaryCountsBySeverity_unassigned(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.StaleImageTag, nil + return obj.Unassigned, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { - return ec.marshalOString2ᚖstring(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNInt2int(ctx, selections, v) }, true, - false, + true, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_staleImageTag(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type String does not have child fields")) +func (ec *executionContext) fieldContext_ImageVulnerabilitySummaryCountsBySeverity_unassigned(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummaryCountsBySeverity", field, false, false, errors.New("field of type Int does not have child fields")) } func (ec *executionContext) _ImageVulnerabilitySuppression_state(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySuppression) (ret graphql.Marshaler) { @@ -3078,7 +3579,7 @@ func (ec *executionContext) unmarshalInputTeamVulnerabilitySummaryFilter(ctx con asMap[k] = v } - fieldsInOrder := [...]string{"environmentName"} + fieldsInOrder := [...]string{"environmentName", "environments", "priority"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -3092,6 +3593,20 @@ func (ec *executionContext) unmarshalInputTeamVulnerabilitySummaryFilter(ctx con return it, err } it.EnvironmentName = data + case "environments": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("environments")) + data, err := ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + it.Environments = data + case "priority": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("priority")) + data, err := ec.unmarshalOCVEPriority2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx, v) + if err != nil { + return it, err + } + it.Priority = data } } return it, nil @@ -3236,6 +3751,25 @@ func (ec *executionContext) _CVE(ctx context.Context, sel ast.SelectionSet, obj } case "cvssScore": out.Values[i] = ec._CVE_cvssScore(ctx, field, obj) + case "priority": + out.Values[i] = ec._CVE_priority(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "epssScore": + out.Values[i] = ec._CVE_epssScore(ctx, field, obj) + case "epssPercentile": + out.Values[i] = ec._CVE_epssPercentile(ctx, field, obj) + case "hasKevEntry": + out.Values[i] = ec._CVE_hasKevEntry(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "knownRansomwareUse": + out.Values[i] = ec._CVE_knownRansomwareUse(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "workloads": field := field @@ -3695,6 +4229,8 @@ func (ec *executionContext) _ImageVulnerability(ctx context.Context, sel ast.Sel if out.Values[i] == graphql.Null { out.Invalids++ } + case "fixVersion": + out.Values[i] = ec._ImageVulnerability_fixVersion(ctx, field, obj) case "suppression": out.Values[i] = ec._ImageVulnerability_suppression(ctx, field, obj) case "severitySince": @@ -3706,6 +4242,20 @@ func (ec *executionContext) _ImageVulnerability(ctx context.Context, sel ast.Sel } case "cvssScore": out.Values[i] = ec._ImageVulnerability_cvssScore(ctx, field, obj) + case "epssScore": + out.Values[i] = ec._ImageVulnerability_epssScore(ctx, field, obj) + case "epssPercentile": + out.Values[i] = ec._ImageVulnerability_epssPercentile(ctx, field, obj) + case "hasKevEntry": + out.Values[i] = ec._ImageVulnerability_hasKevEntry(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "knownRansomwareUse": + out.Values[i] = ec._ImageVulnerability_knownRansomwareUse(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -3926,6 +4476,16 @@ func (ec *executionContext) _ImageVulnerabilitySummary(ctx context.Context, sel if out.Values[i] == graphql.Null { out.Invalids++ } + case "countsBySeverity": + out.Values[i] = ec._ImageVulnerabilitySummary_countsBySeverity(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "countsByPriority": + out.Values[i] = ec._ImageVulnerabilitySummary_countsByPriority(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "low": out.Values[i] = ec._ImageVulnerabilitySummary_low(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -3978,6 +4538,119 @@ func (ec *executionContext) _ImageVulnerabilitySummary(ctx context.Context, sel return out } +var imageVulnerabilitySummaryCountsByPriorityImplementors = []string{"ImageVulnerabilitySummaryCountsByPriority"} + +func (ec *executionContext) _ImageVulnerabilitySummaryCountsByPriority(ctx context.Context, sel ast.SelectionSet, obj *vulnerability.ImageVulnerabilitySummaryCountsByPriority) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, imageVulnerabilitySummaryCountsByPriorityImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ImageVulnerabilitySummaryCountsByPriority") + case "urgent": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsByPriority_urgent(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "highRisk": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsByPriority_highRisk(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "elevatedRisk": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsByPriority_elevatedRisk(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "monitor": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsByPriority_monitor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var imageVulnerabilitySummaryCountsBySeverityImplementors = []string{"ImageVulnerabilitySummaryCountsBySeverity"} + +func (ec *executionContext) _ImageVulnerabilitySummaryCountsBySeverity(ctx context.Context, sel ast.SelectionSet, obj *vulnerability.ImageVulnerabilitySummaryCountsBySeverity) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, imageVulnerabilitySummaryCountsBySeverityImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ImageVulnerabilitySummaryCountsBySeverity") + case "critical": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsBySeverity_critical(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "high": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsBySeverity_high(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "medium": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsBySeverity_medium(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "low": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsBySeverity_low(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "unassigned": + out.Values[i] = ec._ImageVulnerabilitySummaryCountsBySeverity_unassigned(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var imageVulnerabilitySuppressionImplementors = []string{"ImageVulnerabilitySuppression"} func (ec *executionContext) _ImageVulnerabilitySuppression(ctx context.Context, sel ast.SelectionSet, obj *vulnerability.ImageVulnerabilitySuppression) graphql.Marshaler { @@ -4872,6 +5545,16 @@ func (ec *executionContext) marshalNCVEOrderField2githubᚗcomᚋnaisᚋapiᚋin return v } +func (ec *executionContext) unmarshalNCVEPriority2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx context.Context, v any) (vulnerability.CVEPriority, error) { + var res vulnerability.CVEPriority + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNCVEPriority2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx context.Context, sel ast.SelectionSet, v vulnerability.CVEPriority) graphql.Marshaler { + return v +} + func (ec *executionContext) marshalNContainerImageSBOM2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐContainerImageSBOM(ctx context.Context, sel ast.SelectionSet, v vulnerability.ContainerImageSBOM) graphql.Marshaler { return ec._ContainerImageSBOM(ctx, sel, &v) } @@ -5076,6 +5759,14 @@ func (ec *executionContext) marshalNImageVulnerabilitySummary2ᚖgithubᚗcomᚋ return ec._ImageVulnerabilitySummary(ctx, sel, v) } +func (ec *executionContext) marshalNImageVulnerabilitySummaryCountsByPriority2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐImageVulnerabilitySummaryCountsByPriority(ctx context.Context, sel ast.SelectionSet, v vulnerability.ImageVulnerabilitySummaryCountsByPriority) graphql.Marshaler { + return ec._ImageVulnerabilitySummaryCountsByPriority(ctx, sel, &v) +} + +func (ec *executionContext) marshalNImageVulnerabilitySummaryCountsBySeverity2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐImageVulnerabilitySummaryCountsBySeverity(ctx context.Context, sel ast.SelectionSet, v vulnerability.ImageVulnerabilitySummaryCountsBySeverity) graphql.Marshaler { + return ec._ImageVulnerabilitySummaryCountsBySeverity(ctx, sel, &v) +} + func (ec *executionContext) unmarshalNImageVulnerabilitySuppressionState2githubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐImageVulnerabilitySuppressionState(ctx context.Context, v any) (vulnerability.ImageVulnerabilitySuppressionState, error) { var res vulnerability.ImageVulnerabilitySuppressionState err := res.UnmarshalGQL(v) @@ -5341,6 +6032,22 @@ func (ec *executionContext) unmarshalOCVEOrder2ᚖgithubᚗcomᚋnaisᚋapiᚋin return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOCVEPriority2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx context.Context, v any) (*vulnerability.CVEPriority, error) { + if v == nil { + return nil, nil + } + var res = new(vulnerability.CVEPriority) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOCVEPriority2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx context.Context, sel ast.SelectionSet, v *vulnerability.CVEPriority) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + func (ec *executionContext) unmarshalOCVEWorkloadsFilter2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEWorkloadsFilter(ctx context.Context, v any) (*vulnerability.CVEWorkloadsFilter, error) { if v == nil { return nil, nil diff --git a/internal/graph/issues.resolvers.go b/internal/graph/issues.resolvers.go index 558894d1f..4befe6098 100644 --- a/internal/graph/issues.resolvers.go +++ b/internal/graph/issues.resolvers.go @@ -48,6 +48,14 @@ func (r *externalIngressCriticalVulnerabilityIssueResolver) Workload(ctx context return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) } +func (r *externalIngressUrgentVulnerabilityIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (*team.TeamEnvironment, error) { + return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) +} + +func (r *externalIngressUrgentVulnerabilityIssueResolver) Workload(ctx context.Context, obj *issue.ExternalIngressUrgentVulnerabilityIssue) (workload.Workload, error) { + return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) +} + // Deprecated: superseded by WorkloadProblemIssue. Kept for GraphQL backwards compatibility. func (r *failedSynchronizationIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.FailedSynchronizationIssue) (*team.TeamEnvironment, error) { return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) @@ -177,6 +185,10 @@ func (r *Resolver) ExternalIngressCriticalVulnerabilityIssue() gengql.ExternalIn return &externalIngressCriticalVulnerabilityIssueResolver{r} } +func (r *Resolver) ExternalIngressUrgentVulnerabilityIssue() gengql.ExternalIngressUrgentVulnerabilityIssueResolver { + return &externalIngressUrgentVulnerabilityIssueResolver{r} +} + func (r *Resolver) FailedSynchronizationIssue() gengql.FailedSynchronizationIssueResolver { return &failedSynchronizationIssueResolver{r} } @@ -232,6 +244,7 @@ type ( deprecatedIngressIssueResolver struct{ *Resolver } deprecatedRegistryIssueResolver struct{ *Resolver } externalIngressCriticalVulnerabilityIssueResolver struct{ *Resolver } + externalIngressUrgentVulnerabilityIssueResolver struct{ *Resolver } failedSynchronizationIssueResolver struct{ *Resolver } invalidSpecIssueResolver struct{ *Resolver } issueConnectionResolver struct{ *Resolver } diff --git a/internal/graph/schema/issues.graphqls b/internal/graph/schema/issues.graphqls index 81d65dbd6..7a8a996b7 100644 --- a/internal/graph/schema/issues.graphqls +++ b/internal/graph/schema/issues.graphqls @@ -230,6 +230,8 @@ enum IssueType { MISSING_SBOM VULNERABLE_IMAGE EXTERNAL_INGRESS_CRITICAL_VULNERABILITY + @deprecated(reason: "Use EXTERNAL_INGRESS_URGENT_VULNERABILITY.") + EXTERNAL_INGRESS_URGENT_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." APPLICATION_RESTART_LOOP @@ -246,14 +248,35 @@ type VulnerableImageIssue implements Issue & Node { critical: Int! } +"Deprecated: use ExternalIngressUrgentVulnerabilityIssue." type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { + id: ID! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + teamEnvironment: TeamEnvironment! + @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + severity: Severity! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + message: String! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + + workload: Workload! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + cvssScore: Float! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") + ingresses: [String!]! @deprecated(reason: "Use ExternalIngressUrgentVulnerabilityIssue instead.") +} + +"Raised when a workload with external ingresses has one or more urgent vulnerability-priority findings." +type ExternalIngressUrgentVulnerabilityIssue implements Issue & Node { + "The globally unique identifier for this issue." id: ID! + "The team environment where the affected workload is deployed." teamEnvironment: TeamEnvironment! + "The severity assigned to this issue." severity: Severity! + "A human-readable description of the issue." message: String! + "The workload with urgent vulnerabilities and external ingresses." workload: Workload! - cvssScore: Float! + "Number of urgent vulnerabilities on the workload." + priorityUrgent: Int! + "External ingress URLs that expose the workload." ingresses: [String!]! } diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index 4359548fd..9ecf39047 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -51,10 +51,16 @@ input CVEOrder { } enum CVEOrderField { + "Order by CVE identifier." IDENTIFIER + "Order by CVE severity." SEVERITY + "Order by CVSS score." CVSS_SCORE + "Order by number of affected workloads." AFFECTED_WORKLOADS_COUNT + "Order by CVE priority derived from threat intelligence signals." + PRIORITY } extend interface Workload { @@ -248,6 +254,18 @@ input TeamVulnerabilitySummaryFilter { Only return vulnerability summaries for the given environment. """ environmentName: String + + """ + Deprecated: use environmentName instead. + Only one environment is supported if this list is used. + """ + environments: [String!] + @deprecated(reason: "Use environmentName instead. Only one value is supported.") + + """ + Only return vulnerability summaries at or above the given vulnerability priority. + """ + priority: CVEPriority } """ @@ -268,20 +286,26 @@ type ImageVulnerabilitySummary { "Risk score of the image." riskScore: Int! + "Vulnerability counts grouped by severity." + countsBySeverity: ImageVulnerabilitySummaryCountsBySeverity! + + "Vulnerability counts grouped by operational priority." + countsByPriority: ImageVulnerabilitySummaryCountsByPriority! + "Number of vulnerabilities with severity LOW." - low: Int! + low: Int! @deprecated(reason: "Use countsBySeverity.low instead.") "Number of vulnerabilities with severity MEDIUM." - medium: Int! + medium: Int! @deprecated(reason: "Use countsBySeverity.medium instead.") "Number of vulnerabilities with severity HIGH." - high: Int! + high: Int! @deprecated(reason: "Use countsBySeverity.high instead.") "Number of vulnerabilities with severity CRITICAL." - critical: Int! + critical: Int! @deprecated(reason: "Use countsBySeverity.critical instead.") "Number of vulnerabilities with severity UNASSIGNED." - unassigned: Int! + unassigned: Int! @deprecated(reason: "Use countsBySeverity.unassigned instead.") "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -294,6 +318,37 @@ type ImageVulnerabilitySummary { staleImageTag: String } +type ImageVulnerabilitySummaryCountsBySeverity { + "Number of vulnerabilities with severity CRITICAL." + critical: Int! + + "Number of vulnerabilities with severity HIGH." + high: Int! + + "Number of vulnerabilities with severity MEDIUM." + medium: Int! + + "Number of vulnerabilities with severity LOW." + low: Int! + + "Number of vulnerabilities with severity UNASSIGNED." + unassigned: Int! +} + +type ImageVulnerabilitySummaryCountsByPriority { + "Known-exploited vulnerabilities that require immediate action." + urgent: Int! + + "Vulnerabilities with strong exploitation indicators." + highRisk: Int! + + "Vulnerabilities with elevated exploitation risk." + elevatedRisk: Int! + + "Vulnerabilities that should be monitored." + monitor: Int! +} + type ImageVulnerabilityConnection { "Information to aid in pagination." pageInfo: PageInfo! @@ -361,6 +416,9 @@ type ImageVulnerability implements Node { "Package name of the vulnerability." package: String! + "First known package version that contains a fix." + fixVersion: String + suppression: ImageVulnerabilitySuppression "Timestamp of when the vulnerability got its current severity." @@ -371,6 +429,29 @@ type ImageVulnerability implements Node { "CVSS score of the vulnerability." cvssScore: Float + + "EPSS score of the vulnerability." + epssScore: Float + + "EPSS percentile of the vulnerability (0-1)." + epssPercentile: Float + + "Whether the vulnerability has a CISA KEV entry." + hasKevEntry: Boolean! + + "Whether the vulnerability has known ransomware use." + knownRansomwareUse: Boolean! +} + +enum CVEPriority { + "Vulnerability is known to be actively exploited and requires immediate action." + ACT_NOW + "Vulnerability is associated with ransomware or has a high EPSS percentile." + HIGH + "Vulnerability has a critical or high severity and elevated EPSS percentile." + ELEVATED + "Vulnerability requires monitoring but no immediate action." + MONITOR } type CVE implements Node { @@ -395,6 +476,21 @@ type CVE implements Node { "CVSS score of the CVE." cvssScore: Float + "Priority of the CVE based on threat intelligence signals." + priority: CVEPriority! + + "EPSS score of the CVE (probability of exploitation)." + epssScore: Float + + "EPSS percentile of the CVE." + epssPercentile: Float + + "Whether the CVE has a Known Exploited Vulnerability (KEV) entry." + hasKevEntry: Boolean! + + "Whether the CVE is known to be used in ransomware attacks." + knownRansomwareUse: Boolean! + "Affected workloads" workloads( "Get the first n items in the connection. This can be used in combination with the after parameter." @@ -499,12 +595,20 @@ input ImageVulnerabilityOrder { } enum ImageVulnerabilityOrderField { + "Order by vulnerability identifier." IDENTIFIER + "Order by vulnerability severity." SEVERITY + "Order by when the vulnerability received its current severity." SEVERITY_SINCE + "Order by affected package name." PACKAGE + "Order by suppression state." STATE + "Order by whether the vulnerability is suppressed." SUPPRESSED + "Order by vulnerability priority derived from threat intelligence signals." + PRIORITY } type WorkloadVulnerabilitySummary implements Node { @@ -547,29 +651,57 @@ enum VulnerabilitySummaryOrderByField { """ ENVIRONMENT """ - Order by risk score" + Order by risk score. """ VULNERABILITY_RISK_SCORE """ - Order by vulnerability severity critical" + Order by vulnerability severity critical. """ VULNERABILITY_SEVERITY_CRITICAL """ - Order by vulnerability severity high" + Order by vulnerability severity high. """ VULNERABILITY_SEVERITY_HIGH """ - Order by vulnerability severity medium" + Order by vulnerability severity medium. """ VULNERABILITY_SEVERITY_MEDIUM """ - Order by vulnerability severity low" + Order by vulnerability severity low. """ VULNERABILITY_SEVERITY_LOW """ - Order by vulnerability severity unassigned" + Order by vulnerability severity unassigned. """ VULNERABILITY_SEVERITY_UNASSIGNED + """ + Order by the number of urgent vulnerabilities. + + Urgent vulnerabilities are known to be actively exploited and should be + prioritized for immediate action. + """ + VULNERABILITY_PRIORITY_URGENT + """ + Order by the number of high-risk vulnerabilities. + + High-risk vulnerabilities are not known exploited, but have strong + exploitation indicators such as known ransomware use or very high EPSS. + """ + VULNERABILITY_PRIORITY_HIGH_RISK + """ + Order by the number of elevated-risk vulnerabilities. + + Elevated-risk vulnerabilities are lower priority than high-risk, but still + have meaningful exploitation risk signals. + """ + VULNERABILITY_PRIORITY_ELEVATED_RISK + """ + Order by the number of monitor vulnerabilities. + + Monitor vulnerabilities should be tracked, but do not currently indicate the + same operational urgency as the higher priority buckets. + """ + VULNERABILITY_PRIORITY_MONITOR } type TenantVulnerabilitySummary { diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 1fc65db5d..f850cfec3 100644 --- a/internal/issue/checker/workload_v13s.go +++ b/internal/issue/checker/workload_v13s.go @@ -19,7 +19,6 @@ const ( type V13sClient interface { ListVulnerabilitySummaries(ctx context.Context, opts ...vulnerabilities.Option) (*vulnerabilities.ListVulnerabilitySummariesResponse, error) - ListWorkloadsForVulnerability(ctx context.Context, vulnerabilityFilter vulnerabilities.VulnerabilityFilter, opts ...vulnerabilities.Option) (*vulnerabilities.ListWorkloadsForVulnerabilityResponse, error) } type fakeV13sClient struct{} @@ -40,6 +39,8 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... VulnerabilitySummary: &vulnerabilities.Summary{ Critical: 5, RiskScore: 250, + ActNow: 2, + HighRisk: 3, }, SbomStatus: &vulnerabilities.SbomStatusInfo{ Status: vulnerabilities.SbomStatus_SBOM_STATUS_READY, @@ -72,6 +73,8 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... VulnerabilitySummary: &vulnerabilities.Summary{ Critical: 5, RiskScore: 250, + ActNow: 2, + HighRisk: 3, }, SbomStatus: &vulnerabilities.SbomStatusInfo{ Status: vulnerabilities.SbomStatus_SBOM_STATUS_READY, @@ -109,47 +112,6 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... }, nil } -func (f fakeV13sClient) ListWorkloadsForVulnerability(ctx context.Context, vulnerabilityFilter vulnerabilities.VulnerabilityFilter, opts ...vulnerabilities.Option) (*vulnerabilities.ListWorkloadsForVulnerabilityResponse, error) { - if vulnerabilityFilter.CvssScore == nil || *vulnerabilityFilter.CvssScore != 10.0 { - return &vulnerabilities.ListWorkloadsForVulnerabilityResponse{}, nil - } - - return &vulnerabilities.ListWorkloadsForVulnerabilityResponse{ - Nodes: []*vulnerabilities.WorkloadForVulnerability{ - { - WorkloadRef: &vulnerabilities.Workload{ - Cluster: "dev-gcp", - Namespace: "devteam", - Type: "app", - Name: "vulnerable", - }, - Vulnerability: &vulnerabilities.Vulnerability{ - Cve: &vulnerabilities.Cve{ - Id: "CVE-FAKE-0001", - CvssScore: new(10.0), - }, - CvssScore: new(10.0), - }, - }, - { - WorkloadRef: &vulnerabilities.Workload{ - Cluster: "dev-gcp", - Namespace: "fake-team", - Type: "app", - Name: "fake-external-app", - }, - Vulnerability: &vulnerabilities.Vulnerability{ - Cve: &vulnerabilities.Cve{ - Id: "CVE-FAKE-0002", - CvssScore: new(10.0), - }, - CvssScore: new(10.0), - }, - }, - }, - }, nil -} - func (w Workload) vulnerabilities(ctx context.Context) []*Issue { mapType := func(s string) (issue.ResourceType, bool) { if s == "job" { @@ -181,23 +143,23 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { continue } - if node.VulnerabilitySummary != nil && (node.VulnerabilitySummary.Critical > 0 || node.VulnerabilitySummary.RiskScore > 100) { + summary := node.VulnerabilitySummary + if summary != nil && summary.ActNow > 0 { ret = append(ret, &Issue{ IssueType: issue.IssueTypeVulnerableImage, ResourceType: workloadType, ResourceName: node.Workload.GetName(), Team: node.Workload.GetNamespace(), Env: environmentmapper.EnvironmentName(node.Workload.GetCluster()), - Severity: issue.SeverityWarning, + Severity: issue.SeverityCritical, Message: fmt.Sprintf( - "Image '%s' has %d critical vulnerabilities and a risk score of %d", + "Image '%s' has %d urgent vulnerabilities", node.Workload.ImageName, - node.VulnerabilitySummary.Critical, - node.VulnerabilitySummary.RiskScore, + summary.ActNow, ), IssueDetails: issue.VulnerableImageIssueDetails{ - Critical: int(node.VulnerabilitySummary.Critical), - RiskScore: int(node.VulnerabilitySummary.RiskScore), + Critical: int(summary.Critical), + RiskScore: int(summary.RiskScore), }, }) } @@ -222,25 +184,12 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { } } - cvss := 10.0 - workloadsForVulnerability, err := w.V13sClient.ListWorkloadsForVulnerability( - ctx, - vulnerabilities.VulnerabilityFilter{CvssScore: &cvss}, - vulnerabilities.Limit(v13sQueryLimit), - vulnerabilities.ExcludeClustersFilter("management"), - ) - if err != nil { - w.log.WithError(err).Error("fetch workloads for vulnerabilities with cvss score") - return ret - } - externalIngressesByWorkload := w.externalIngressesByWorkload() - seen := map[string]struct{}{} - for _, node := range workloadsForVulnerability.GetNodes() { - workloadRef := node.GetWorkloadRef() - vulnerability := node.GetVulnerability() - if workloadRef == nil || vulnerability == nil { + seenActNow := map[string]struct{}{} + for _, node := range resp.GetNodes() { + workloadRef := node.GetWorkload() + if workloadRef == nil { continue } @@ -249,9 +198,13 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { continue } + if node.VulnerabilitySummary == nil || node.VulnerabilitySummary.ActNow == 0 { + continue + } + env := environmentmapper.EnvironmentName(workloadRef.GetCluster()) key := workloadKey(env, workloadRef.GetNamespace(), workloadRef.GetName()) - if _, exists := seen[key]; exists { + if _, exists := seenActNow[key]; exists { continue } @@ -259,23 +212,23 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { if len(externalIngresses) == 0 { continue } - seen[key] = struct{}{} + seenActNow[key] = struct{}{} ret = append(ret, &Issue{ - IssueType: issue.IssueTypeExternalIngressCriticalVulnerability, + IssueType: issue.IssueTypeExternalIngressUrgentVulnerability, ResourceType: workloadType, ResourceName: workloadRef.GetName(), Team: workloadRef.GetNamespace(), Env: env, Severity: issue.SeverityCritical, Message: fmt.Sprintf( - "Workload with external ingresses %s has a vulnerability with CVSS score %.1f", - strings.Join(externalIngresses, ", "), - cvss, + "Workload '%s' (exposed via external ingress) has %d urgent vulnerabilities", + workloadRef.GetName(), + node.VulnerabilitySummary.ActNow, ), - IssueDetails: issue.ExternalIngressCriticalVulnerabilityIssueDetails{ - CvssScore: cvss, - Ingresses: externalIngresses, + IssueDetails: issue.ExternalIngressUrgentVulnerabilityIssueDetails{ + PriorityUrgent: int(node.VulnerabilitySummary.ActNow), + Ingresses: externalIngresses, }, }) } diff --git a/internal/issue/checker/workload_v13s_test.go b/internal/issue/checker/workload_v13s_test.go index 7810737dd..20cc57138 100644 --- a/internal/issue/checker/workload_v13s_test.go +++ b/internal/issue/checker/workload_v13s_test.go @@ -16,18 +16,32 @@ import ( ) type staticV13sClient struct { - workloads []*vulnerabilities.WorkloadForVulnerability + summaries []*vulnerabilities.WorkloadSummary } func (s staticV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ...vulnerabilities.Option) (*vulnerabilities.ListVulnerabilitySummariesResponse, error) { - return &vulnerabilities.ListVulnerabilitySummariesResponse{}, nil + return &vulnerabilities.ListVulnerabilitySummariesResponse{Nodes: s.summaries}, nil } -func (s staticV13sClient) ListWorkloadsForVulnerability(ctx context.Context, vulnerabilityFilter vulnerabilities.VulnerabilityFilter, opts ...vulnerabilities.Option) (*vulnerabilities.ListWorkloadsForVulnerabilityResponse, error) { - return &vulnerabilities.ListWorkloadsForVulnerabilityResponse{Nodes: s.workloads}, nil +func TestVulnerabilities_ExternalIngressActNowIssue(t *testing.T) { + tests := []struct { + name string + workloadName string + expectedIngress string + wantIssue bool + }{ + {name: "external ingress class", workloadName: "ext-app", expectedIngress: "https://ext.example.com", wantIssue: true}, + {name: "internal ingress class", workloadName: "internal-only", wantIssue: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testVulnerabilitiesExternalIngressActNowIssue(t, tt.workloadName, tt.expectedIngress, tt.wantIssue) + }) + } } -func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { +func testVulnerabilitiesExternalIngressActNowIssue(t *testing.T, workloadName, expectedIngress string, wantIssue bool) { ctx := context.Background() scheme, err := kubernetes.NewScheme() @@ -58,52 +72,79 @@ func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { workload := Workload{ AppWatcher: *appWatcher, IngressWatcher: *ingressWatcher, - V13sClient: staticV13sClient{workloads: []*vulnerabilities.WorkloadForVulnerability{ + V13sClient: staticV13sClient{summaries: []*vulnerabilities.WorkloadSummary{ { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "ext-app"}, - Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(10.0), Cve: &vulnerabilities.Cve{CvssScore: new(10.0)}}, + Workload: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: workloadName}, + VulnerabilitySummary: &vulnerabilities.Summary{ + Critical: 2, + RiskScore: 100, + ActNow: 2, + }, }, { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "ext-app"}, - Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(10.0), Cve: &vulnerabilities.Cve{CvssScore: new(10.0)}}, + Workload: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: workloadName}, + VulnerabilitySummary: &vulnerabilities.Summary{ + Critical: 2, + RiskScore: 100, + ActNow: 2, + }, }, { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "internal-only"}, - Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(10.0), Cve: &vulnerabilities.Cve{CvssScore: new(10.0)}}, + Workload: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "non-existing-workload"}, + VulnerabilitySummary: &vulnerabilities.Summary{ + Critical: 2, + RiskScore: 100, + ActNow: 2, + }, }, { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "ext-app"}, - Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(9.9), Cve: &vulnerabilities.Cve{CvssScore: new(9.9)}}, + Workload: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: workloadName}, + VulnerabilitySummary: &vulnerabilities.Summary{ + ActNow: 0, + }, }, }}, log: logrus.New(), } issues := workload.vulnerabilities(ctx) + actNowIssues := make([]*Issue, 0) + for i := range issues { + if issues[i].IssueType == issue.IssueTypeExternalIngressUrgentVulnerability { + actNowIssues = append(actNowIssues, issues[i]) + } + } + + if !wantIssue { + if len(actNowIssues) != 0 { + t.Fatalf("expected 0 external ingress act-now issues, got %d", len(actNowIssues)) + } + return + } - if len(issues) != 1 { - t.Fatalf("expected 1 issue, got %d", len(issues)) + if len(actNowIssues) != 1 { + t.Fatalf("expected 1 external ingress act-now issue, got %d", len(actNowIssues)) } - got := issues[0] - if got.IssueType != issue.IssueTypeExternalIngressCriticalVulnerability { - t.Fatalf("expected issue type %s, got %s", issue.IssueTypeExternalIngressCriticalVulnerability, got.IssueType) + got := actNowIssues[0] + if got.IssueType != issue.IssueTypeExternalIngressUrgentVulnerability { + t.Fatalf("expected issue type %s, got %s", issue.IssueTypeExternalIngressUrgentVulnerability, got.IssueType) } - if got.ResourceName != "ext-app" { - t.Fatalf("expected resource ext-app, got %s", got.ResourceName) + if got.ResourceName != workloadName { + t.Fatalf("expected resource %s, got %s", workloadName, got.ResourceName) } - details, ok := got.IssueDetails.(issue.ExternalIngressCriticalVulnerabilityIssueDetails) + details, ok := got.IssueDetails.(issue.ExternalIngressUrgentVulnerabilityIssueDetails) if !ok { - t.Fatalf("expected external ingress critical details, got %T", got.IssueDetails) + t.Fatalf("expected external ingress act-now details, got %T", got.IssueDetails) } - if details.CvssScore != 10.0 { - t.Fatalf("expected CVSS 10.0, got %v", details.CvssScore) + if details.PriorityUrgent != 2 { + t.Fatalf("expected priorityUrgent 2, got %v", details.PriorityUrgent) } - if len(details.Ingresses) != 1 || details.Ingresses[0] != "https://ext.example.com" { + if len(details.Ingresses) != 1 || details.Ingresses[0] != expectedIngress { t.Fatalf("expected only external ingress URL, got %+v", details.Ingresses) } } diff --git a/internal/issue/model.go b/internal/issue/model.go index 9a6e9b10a..03fa8bf3d 100644 --- a/internal/issue/model.go +++ b/internal/issue/model.go @@ -184,6 +184,11 @@ type VulnerableImageIssueDetails struct { Critical int `json:"critical"` } +type ExternalIngressUrgentVulnerabilityIssueDetails struct { + PriorityUrgent int `json:"priorityUrgent"` + Ingresses []string `json:"ingresses"` +} + type ExternalIngressCriticalVulnerabilityIssueDetails struct { CvssScore float64 `json:"cvssScore"` Ingresses []string `json:"ingresses"` @@ -208,6 +213,7 @@ const ( IssueTypeVulnerableImage IssueType = "VULNERABLE_IMAGE" IssueTypeMissingSBOM IssueType = "MISSING_SBOM" IssueTypeExternalIngressCriticalVulnerability IssueType = "EXTERNAL_INGRESS_CRITICAL_VULNERABILITY" + IssueTypeExternalIngressUrgentVulnerability IssueType = "EXTERNAL_INGRESS_URGENT_VULNERABILITY" IssueTypeUnleashReleaseChannel IssueType = "UNLEASH_RELEASE_CHANNEL" IssueTypeApplicationRestartLoop IssueType = "APPLICATION_RESTART_LOOP" ) @@ -227,6 +233,7 @@ var AllIssueType = []IssueType{ IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressCriticalVulnerability, + IssueTypeExternalIngressUrgentVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop, } @@ -238,7 +245,8 @@ func (e IssueType) IsValid() bool { IssueTypeNoRunningInstances, IssueTypeLastRunFailed, IssueTypeWorkloadProblem, IssueTypeInvalidSpec, IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressCriticalVulnerability, - IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop: + IssueTypeExternalIngressUrgentVulnerability, IssueTypeUnleashReleaseChannel, + IssueTypeApplicationRestartLoop: return true } return false @@ -495,6 +503,15 @@ func (VulnerableImageIssue) IsIssue() {} func (VulnerableImageIssue) IsNode() {} +type ExternalIngressUrgentVulnerabilityIssue struct { + Base + ExternalIngressUrgentVulnerabilityIssueDetails +} + +func (ExternalIngressUrgentVulnerabilityIssue) IsIssue() {} + +func (ExternalIngressUrgentVulnerabilityIssue) IsNode() {} + type ExternalIngressCriticalVulnerabilityIssue struct { Base ExternalIngressCriticalVulnerabilityIssueDetails diff --git a/internal/issue/queries.go b/internal/issue/queries.go index ad1ab7401..9906ba79b 100644 --- a/internal/issue/queries.go +++ b/internal/issue/queries.go @@ -206,6 +206,15 @@ func convert(issue *issuesql.Issue) (Issue, error) { return &MissingSbomIssue{ Base: base, }, nil + case IssueTypeExternalIngressUrgentVulnerability: + d, err := unmarshal[ExternalIngressUrgentVulnerabilityIssueDetails](issue.IssueDetails) + if err != nil { + return nil, err + } + return &ExternalIngressUrgentVulnerabilityIssue{ + Base: base, + ExternalIngressUrgentVulnerabilityIssueDetails: *d, + }, nil case IssueTypeExternalIngressCriticalVulnerability: d, err := unmarshal[ExternalIngressCriticalVulnerabilityIssueDetails](issue.IssueDetails) if err != nil { diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index 7ac12556b..310cbd8f8 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -77,15 +77,21 @@ func createFakeData(ctx context.Context) *fakeData { func createWorkloadSummary(env, team, workloadType, name, image string, vulnFactor int32) *vulnerabilities.WorkloadSummary { imageName, imageTag := workload.FormatImageReferenceForLookup(image) summary := &vulnerabilities.Summary{ - Critical: vulnFactor, - High: vulnFactor * 2, - Medium: vulnFactor + 2, - Low: vulnFactor + 1, - Unassigned: vulnFactor, - Total: vulnFactor + (vulnFactor * 2) + (vulnFactor + 2) + (vulnFactor + 1) + vulnFactor, - RiskScore: vulnFactor*10 + (vulnFactor*2)*5 + (vulnFactor+2)*3 + (vulnFactor + 1) + vulnFactor*5, - HasSbom: true, - LastUpdated: timestamppb.New(time.Now()), + Critical: vulnFactor, + High: vulnFactor * 2, + Medium: vulnFactor + 2, + Low: vulnFactor + 1, + Unassigned: vulnFactor, + Total: vulnFactor + (vulnFactor * 2) + (vulnFactor + 2) + (vulnFactor + 1) + vulnFactor, + RiskScore: vulnFactor*10 + (vulnFactor*2)*5 + (vulnFactor+2)*3 + (vulnFactor + 1) + vulnFactor*5, + HasSbom: true, + LastUpdated: timestamppb.New(time.Now()), + ActNow: vulnFactor, + HighRisk: vulnFactor * 2, + ElevatedRisk: vulnFactor * 3, + Monitor: vulnFactor * 4, + HighEpssCount: vulnFactor * 2, + TopPriority: vulnerabilities.Priority_PRIORITY_ACT_NOW, } if name == "no-errors" { @@ -118,38 +124,43 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact func createVulnerabilities(w *vulnerabilities.WorkloadSummary) []*vulnerabilities.Vulnerability { findings := make([]*vulnerabilities.Vulnerability, 0) + epssScore := 0.85 + epssPercentile := 0.973 for i := range w.VulnerabilitySummary.Critical { - findings = append(findings, createVulnerability(vulnerabilities.Severity_CRITICAL, fmt.Sprintf("some-component-%d", i))) + findings = append(findings, createVulnerability(vulnerabilities.Severity_CRITICAL, fmt.Sprintf("some-component-%d", i), &epssScore, &epssPercentile, true, false)) } for i := range w.VulnerabilitySummary.High { - findings = append(findings, createVulnerability(vulnerabilities.Severity_HIGH, fmt.Sprintf("some-component-%d", i))) + findings = append(findings, createVulnerability(vulnerabilities.Severity_HIGH, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } for i := range w.VulnerabilitySummary.Medium { - findings = append(findings, createVulnerability(vulnerabilities.Severity_MEDIUM, fmt.Sprintf("some-component-%d", i))) + findings = append(findings, createVulnerability(vulnerabilities.Severity_MEDIUM, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } for i := range w.VulnerabilitySummary.Low { - findings = append(findings, createVulnerability(vulnerabilities.Severity_LOW, fmt.Sprintf("some-component-%d", i))) + findings = append(findings, createVulnerability(vulnerabilities.Severity_LOW, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } for i := range w.VulnerabilitySummary.Unassigned { - findings = append(findings, createVulnerability(vulnerabilities.Severity_UNASSIGNED, fmt.Sprintf("some-component-%d", i))) + findings = append(findings, createVulnerability(vulnerabilities.Severity_UNASSIGNED, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } return findings } -func createVulnerability(severity vulnerabilities.Severity, componentName string) *vulnerabilities.Vulnerability { +func createVulnerability(severity vulnerabilities.Severity, componentName string, epssScore, epssPercentile *float64, hasKevEntry, knownRansomwareUse bool) *vulnerabilities.Vulnerability { return &vulnerabilities.Vulnerability{ Id: uuid.New().String(), Package: fmt.Sprintf("pkg:golang/%s@v2.0.8?type=module", componentName), Cve: &vulnerabilities.Cve{ - Id: fmt.Sprintf("CVE-2024-%d", rand.IntN(100000)), - Title: "title for " + componentName, - Description: "desc for " + componentName, - Link: "", - Severity: severity, - References: nil, + Id: fmt.Sprintf("CVE-2024-%d", rand.IntN(100000)), + Title: "title for " + componentName, + Description: "desc for " + componentName, + Link: "", + Severity: severity, + References: nil, + EpssScore: epssScore, + EpssPercentile: epssPercentile, + HasKevEntry: hasKevEntry, + KnownRansomwareUse: knownRansomwareUse, }, LatestVersion: "", - // TODO: check if suppression is ever nil in protobuf - Suppression: nil, + Suppression: nil, } } diff --git a/internal/vulnerability/fake/v13s.go b/internal/vulnerability/fake/v13s.go index f623006bc..3e5480e44 100644 --- a/internal/vulnerability/fake/v13s.go +++ b/internal/vulnerability/fake/v13s.go @@ -189,13 +189,20 @@ func (f *fakeVulnerabilitiesClient) GetVulnerabilitySummaryTimeSeries(ctx contex resp := &vulnerabilities.GetVulnerabilitySummaryTimeSeriesResponse{ Points: []*vulnerabilities.VulnerabilitySummaryPoint{ { - Total: 1, - Critical: 1, - High: 1, - Medium: 1, - Low: 1, - Unassigned: 1, - BucketTime: timestamppb.New(time.Now()), + Total: 1, + Critical: 1, + High: 1, + Medium: 1, + Low: 1, + Unassigned: 1, + RiskScore: 10, + ActNow: 1, + HighRisk: 2, + ElevatedRisk: 3, + Monitor: 4, + HighEpssCount: 2, + TopPriority: vulnerabilities.Priority_PRIORITY_ACT_NOW, + BucketTime: timestamppb.New(time.Now()), }, }, } diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index c1ead8f2c..cf4a68583 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -46,8 +46,13 @@ type ImageVulnerability struct { Identifier string `json:"identifier"` Severity ImageVulnerabilitySeverity `json:"severity"` CvssScore *float64 `json:"cvssScore"` + EpssScore *float64 `json:"epssScore"` + EpssPercentile *float64 `json:"epssPercentile"` + HasKevEntry bool `json:"hasKevEntry"` + KnownRansomwareUse bool `json:"knownRansomwareUse"` Description string `json:"description"` Package string `json:"package"` + FixVersion *string `json:"fixVersion,omitempty"` SeveritySince *time.Time `json:"severitySince"` Suppression *ImageVulnerabilitySuppression `json:"suppression"` VulnerabilityDetailsLink string `json:"vulnerabilityDetailsLink"` @@ -71,15 +76,32 @@ type ImageVulnerabilitySuppression struct { } type ImageVulnerabilitySummary struct { - Total int `json:"total"` - RiskScore int `json:"riskScore"` - Low int `json:"low"` - Medium int `json:"medium"` - High int `json:"high"` - Critical int `json:"critical"` - Unassigned int `json:"unassigned"` - LastUpdated *time.Time `json:"lastUpdated"` - StaleImageTag *string `json:"staleImageTag"` + Total int `json:"total"` + RiskScore int `json:"riskScore"` + CountsBySeverity ImageVulnerabilitySummaryCountsBySeverity `json:"countsBySeverity"` + CountsByPriority ImageVulnerabilitySummaryCountsByPriority `json:"countsByPriority"` + Low int `json:"low"` + Medium int `json:"medium"` + High int `json:"high"` + Critical int `json:"critical"` + Unassigned int `json:"unassigned"` + LastUpdated *time.Time `json:"lastUpdated"` + StaleImageTag *string `json:"staleImageTag"` +} + +type ImageVulnerabilitySummaryCountsBySeverity struct { + Critical int `json:"critical"` + High int `json:"high"` + Medium int `json:"medium"` + Low int `json:"low"` + Unassigned int `json:"unassigned"` +} + +type ImageVulnerabilitySummaryCountsByPriority struct { + Urgent int `json:"urgent"` + HighRisk int `json:"highRisk"` + ElevatedRisk int `json:"elevatedRisk"` + Monitor int `json:"monitor"` } type ImageVulnerabilityOrderField string @@ -213,15 +235,19 @@ type VulnerabilitySummaryOrder struct { type VulnerabilitySummaryOrderByField string const ( - VulnerabilitySummaryOrderByFieldName VulnerabilitySummaryOrderByField = "NAME" - VulnerabilitySummaryOrderByFieldEnvironment VulnerabilitySummaryOrderByField = "ENVIRONMENT" - VulnerabilitySummaryOrderByFieldVulnerabilityRiskScore VulnerabilitySummaryOrderByField = "VULNERABILITY_RISK_SCORE" - VulnerabilitySummaryOrderByFieldVulnerabilitySeverityCritical VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_CRITICAL" - VulnerabilitySummaryOrderByFieldVulnerabilitySeverityHigh VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_HIGH" - VulnerabilitySummaryOrderByFieldVulnerabilitySeverityMedium VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_MEDIUM" - VulnerabilitySummaryOrderByFieldVulnerabilitySeverityLow VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_LOW" - VulnerabilitySummaryOrderByFieldVulnerabilitySeverityUnassigned VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_UNASSIGNED" - VulnerabilitySummaryOrderByFieldVulnerabilityLastScanned VulnerabilitySummaryOrderByField = "VULNERABILITY_LAST_SCANNED" + VulnerabilitySummaryOrderByFieldName VulnerabilitySummaryOrderByField = "NAME" + VulnerabilitySummaryOrderByFieldEnvironment VulnerabilitySummaryOrderByField = "ENVIRONMENT" + VulnerabilitySummaryOrderByFieldVulnerabilityRiskScore VulnerabilitySummaryOrderByField = "VULNERABILITY_RISK_SCORE" + VulnerabilitySummaryOrderByFieldVulnerabilitySeverityCritical VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_CRITICAL" + VulnerabilitySummaryOrderByFieldVulnerabilitySeverityHigh VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_HIGH" + VulnerabilitySummaryOrderByFieldVulnerabilitySeverityMedium VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_MEDIUM" + VulnerabilitySummaryOrderByFieldVulnerabilitySeverityLow VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_LOW" + VulnerabilitySummaryOrderByFieldVulnerabilitySeverityUnassigned VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_UNASSIGNED" + VulnerabilitySummaryOrderByFieldVulnerabilityLastScanned VulnerabilitySummaryOrderByField = "VULNERABILITY_LAST_SCANNED" + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityUrgent VulnerabilitySummaryOrderByField = "VULNERABILITY_PRIORITY_URGENT" + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityHighRisk VulnerabilitySummaryOrderByField = "VULNERABILITY_PRIORITY_HIGH_RISK" + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityElevatedRisk VulnerabilitySummaryOrderByField = "VULNERABILITY_PRIORITY_ELEVATED_RISK" + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityMonitor VulnerabilitySummaryOrderByField = "VULNERABILITY_PRIORITY_MONITOR" ) var AllVulnerabilitySummaryOrderByField = []VulnerabilitySummaryOrderByField{ @@ -234,11 +260,15 @@ var AllVulnerabilitySummaryOrderByField = []VulnerabilitySummaryOrderByField{ VulnerabilitySummaryOrderByFieldVulnerabilitySeverityLow, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityUnassigned, VulnerabilitySummaryOrderByFieldVulnerabilityLastScanned, + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityUrgent, + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityHighRisk, + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityElevatedRisk, + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityMonitor, } func (e VulnerabilitySummaryOrderByField) IsValid() bool { switch e { - case VulnerabilitySummaryOrderByFieldName, VulnerabilitySummaryOrderByFieldEnvironment, VulnerabilitySummaryOrderByFieldVulnerabilityRiskScore, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityCritical, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityHigh, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityMedium, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityLow, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityUnassigned, VulnerabilitySummaryOrderByFieldVulnerabilityLastScanned: + case VulnerabilitySummaryOrderByFieldName, VulnerabilitySummaryOrderByFieldEnvironment, VulnerabilitySummaryOrderByFieldVulnerabilityRiskScore, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityCritical, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityHigh, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityMedium, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityLow, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityUnassigned, VulnerabilitySummaryOrderByFieldVulnerabilityLastScanned, VulnerabilitySummaryOrderByFieldVulnerabilityPriorityUrgent, VulnerabilitySummaryOrderByFieldVulnerabilityPriorityHighRisk, VulnerabilitySummaryOrderByFieldVulnerabilityPriorityElevatedRisk, VulnerabilitySummaryOrderByFieldVulnerabilityPriorityMonitor: return true } return false @@ -374,7 +404,9 @@ func (e ImageVulnerabilitySuppressionState) MarshalGQL(w io.Writer) { } type TeamVulnerabilitySummaryFilter struct { - EnvironmentName *string `json:"environmentName,omitempty"` + EnvironmentName *string `json:"environmentName,omitempty"` + Environments []string `json:"environments,omitempty"` + Priority *CVEPriority `json:"priority,omitempty"` } type ImageVulnerabilitySample struct { @@ -412,14 +444,57 @@ type VulnerabilityFixSample struct { TotalWorkloads int `json:"totalWorkloads"` } +type CVEPriority string + +const ( + CVEPriorityActNow CVEPriority = "ACT_NOW" + CVEPriorityHigh CVEPriority = "HIGH" + CVEPriorityElevated CVEPriority = "ELEVATED" + CVEPriorityMonitor CVEPriority = "MONITOR" +) + +func (e CVEPriority) IsValid() bool { + switch e { + case CVEPriorityActNow, CVEPriorityHigh, CVEPriorityElevated, CVEPriorityMonitor: + return true + } + return false +} + +func (e CVEPriority) String() string { + return string(e) +} + +func (e *CVEPriority) UnmarshalGQL(v any) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = CVEPriority(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid CVEPriority", str) + } + return nil +} + +func (e CVEPriority) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + type CVE struct { - Identifier string `json:"identifier"` - Severity ImageVulnerabilitySeverity `json:"severity"` - Title string `json:"title"` - Description string `json:"description"` - SeveritySince *time.Time `json:"severitySince,omitempty"` - DetailsLink string `json:"detailsLink"` - CVSSScore *float64 `json:"cvssScore,omitempty"` + Identifier string `json:"identifier"` + Severity ImageVulnerabilitySeverity `json:"severity"` + Title string `json:"title"` + Description string `json:"description"` + SeveritySince *time.Time `json:"severitySince,omitempty"` + DetailsLink string `json:"detailsLink"` + CVSSScore *float64 `json:"cvssScore,omitempty"` + Priority CVEPriority `json:"priority"` + EpssScore *float64 `json:"epssScore,omitempty"` + EpssPercentile *float64 `json:"epssPercentile,omitempty"` + HasKevEntry bool `json:"hasKevEntry"` + KnownRansomwareUse bool `json:"knownRansomwareUse"` // AffectedWorkloads is used to short circuit counting affected workloads in resolvers, // if the only field requested of the workloads field is the total count. @@ -476,6 +551,7 @@ const ( CVEOrderFieldSeverity CVEOrderField = "SEVERITY" CVEOrderFieldCVSSScore CVEOrderField = "CVSS_SCORE" CVEOrderFieldAffectedWorkloadsCount CVEOrderField = "AFFECTED_WORKLOADS_COUNT" + CVEOrderFieldPriority CVEOrderField = "PRIORITY" ) var AllCVEOrderField = []CVEOrderField{ @@ -483,11 +559,12 @@ var AllCVEOrderField = []CVEOrderField{ CVEOrderFieldSeverity, CVEOrderFieldCVSSScore, CVEOrderFieldAffectedWorkloadsCount, + CVEOrderFieldPriority, } func (e CVEOrderField) IsValid() bool { switch e { - case CVEOrderFieldIdentifier, CVEOrderFieldSeverity, CVEOrderFieldCVSSScore, CVEOrderFieldAffectedWorkloadsCount: + case CVEOrderFieldIdentifier, CVEOrderFieldSeverity, CVEOrderFieldCVSSScore, CVEOrderFieldAffectedWorkloadsCount, CVEOrderFieldPriority: return true } return false diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index 79b8b493a..47b0c7793 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -70,6 +70,10 @@ func ListVulnerabilitySummaries(ctx context.Context, s slug.Slug, filter *TeamVu opts = append(opts, vulnerabilities.Offset(page.Offset())) opts = append(opts, vulnerabilities.Limit(page.Limit())) + if filter != nil && filter.Priority != nil { + opts = append(opts, vulnerabilities.PriorityFilter(mapCVEPriorityToPriority(*filter.Priority))) + } + if orderBy != nil { direction := vulnerabilities.Direction_ASC if orderBy.Direction == model.OrderDirectionDesc { @@ -374,6 +378,19 @@ func GetImageVulnerabilitySummary(ctx context.Context, ref string) (*ImageVulner RiskScore: int(sum.GetRiskScore()), LastUpdated: lastUpdated, StaleImageTag: sum.StaleImageTag, + CountsBySeverity: ImageVulnerabilitySummaryCountsBySeverity{ + Critical: int(sum.GetCritical()), + High: int(sum.GetHigh()), + Medium: int(sum.GetMedium()), + Low: int(sum.GetLow()), + Unassigned: int(sum.GetUnassigned()), + }, + CountsByPriority: ImageVulnerabilitySummaryCountsByPriority{ + Urgent: int(sum.GetActNow()), + HighRisk: int(sum.GetHighRisk()), + ElevatedRisk: int(sum.GetElevatedRisk()), + Monitor: int(sum.GetMonitor()), + }, }, nil } @@ -475,6 +492,25 @@ func normalizeFromDate(from time.Time) time.Time { return time.Date(from.Year(), from.Month(), from.Day(), 2, 0, 0, 0, time.UTC) } +func environmentNameFromFilter(filter *TeamVulnerabilitySummaryFilter) (*string, error) { + if filter == nil { + return nil, nil + } + if filter.EnvironmentName != nil && len(filter.Environments) > 0 { + return nil, apierror.Errorf("environmentName and environments cannot be used together") + } + if filter.EnvironmentName != nil { + return filter.EnvironmentName, nil + } + if len(filter.Environments) > 1 { + return nil, apierror.Errorf("environments supports exactly one value") + } + if len(filter.Environments) == 1 { + return &filter.Environments[0], nil + } + return nil, nil +} + func getVulnerabilityHistory(ctx context.Context, opts []vulnerabilities.Option) (*ImageVulnerabilityHistory, error) { resp, err := fromContext(ctx).manager.Client.GetVulnerabilitySummaryTimeSeries(ctx, opts...) if err != nil { @@ -493,6 +529,19 @@ func getVulnerabilityHistory(ctx context.Context, opts []vulnerabilities.Option) Unassigned: int(point.GetUnassigned()), Total: int(point.GetTotal()), RiskScore: int(point.GetRiskScore()), + CountsBySeverity: ImageVulnerabilitySummaryCountsBySeverity{ + Critical: int(point.GetCritical()), + High: int(point.GetHigh()), + Medium: int(point.GetMedium()), + Low: int(point.GetLow()), + Unassigned: int(point.GetUnassigned()), + }, + CountsByPriority: ImageVulnerabilitySummaryCountsByPriority{ + Urgent: int(point.GetActNow()), + HighRisk: int(point.GetHighRisk()), + ElevatedRisk: int(point.GetElevatedRisk()), + Monitor: int(point.GetMonitor()), + }, }, Date: point.GetBucketTime().AsTime(), }) @@ -627,6 +676,8 @@ func ListCVEs(ctx context.Context, page *pagination.Pagination, orderBy *CVEOrde field = vulnerabilities.OrderByCvssScore case CVEOrderFieldAffectedWorkloadsCount: field = vulnerabilities.OrderByAffectedWorkloads + case CVEOrderFieldPriority: + field = vulnerabilities.OrderByPriority default: field = vulnerabilities.OrderByCvssScore } @@ -671,7 +722,7 @@ func GetWorkloadsByCVE(ctx context.Context, cve string, page *pagination.Paginat if err != nil { return nil, apierror.Errorf("list workloads for vulnerability by CVE: %v", err) } - return convertWorkloadNodes(ctx, resp.GetNodes(), page, int32(min(resp.GetPageInfo().GetTotalCount(), math.MaxInt32))) //nolint:gosec + return convertWorkloadNodes(ctx, resp.GetNodes(), page, safeInt32TotalCount(resp.GetPageInfo().GetTotalCount())) } return getWorkloadsByCVE(ctx, cve, page) } @@ -693,7 +744,17 @@ func getWorkloadsByCVE(ctx context.Context, cve string, page *pagination.Paginat if err != nil { return nil, apierror.Errorf("list workloads for vulnerability by CVE: %v", err) } - return convertWorkloadNodes(ctx, resp.GetNodes(), page, int32(min(resp.GetPageInfo().GetTotalCount(), math.MaxInt32))) //nolint:gosec + return convertWorkloadNodes(ctx, resp.GetNodes(), page, safeInt32TotalCount(resp.GetPageInfo().GetTotalCount())) +} + +func safeInt32TotalCount(totalCount int64) int32 { + if totalCount <= 0 { + return 0 + } + if totalCount > int64(math.MaxInt32) { + return math.MaxInt32 + } + return int32(totalCount) } func convertWorkloadNodes(ctx context.Context, nodes []*vulnerabilities.WorkloadForVulnerability, page *pagination.Pagination, totalCount int32) (*WorkloadWithVulnerabilityConnection, error) { diff --git a/internal/vulnerability/queries_test.go b/internal/vulnerability/queries_test.go index f71d4af1e..8bdd9f25a 100644 --- a/internal/vulnerability/queries_test.go +++ b/internal/vulnerability/queries_test.go @@ -2,6 +2,40 @@ package vulnerability import "testing" +func TestEnvironmentNameFromFilter(t *testing.T) { + dev := "dev" + tests := []struct { + name string + filter *TeamVulnerabilitySummaryFilter + want *string + wantErr bool + }{ + {name: "nil filter"}, + {name: "environment name", filter: &TeamVulnerabilitySummaryFilter{EnvironmentName: &dev}, want: &dev}, + {name: "deprecated single environment", filter: &TeamVulnerabilitySummaryFilter{Environments: []string{"dev"}}, want: &dev}, + {name: "multiple deprecated environments", filter: &TeamVulnerabilitySummaryFilter{Environments: []string{"dev", "prod"}}, wantErr: true}, + {name: "both environment filters", filter: &TeamVulnerabilitySummaryFilter{EnvironmentName: &dev, Environments: []string{"prod"}}, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := environmentNameFromFilter(tt.filter) + if (err != nil) != tt.wantErr { + t.Fatalf("environmentNameFromFilter() error = %v, wantErr %v", err, tt.wantErr) + } + if got == nil || tt.want == nil { + if got != tt.want { + t.Fatalf("environmentNameFromFilter() = %v, want %v", got, tt.want) + } + return + } + if *got != *tt.want { + t.Fatalf("environmentNameFromFilter() = %q, want %q", *got, *tt.want) + } + }) + } +} + func TestSplitImage(t *testing.T) { tests := []struct { name string diff --git a/internal/vulnerability/sortfilter.go b/internal/vulnerability/sortfilter.go index cad3014d3..633941ab8 100644 --- a/internal/vulnerability/sortfilter.go +++ b/internal/vulnerability/sortfilter.go @@ -16,17 +16,22 @@ var SortFilterImageVulnerabilities = map[ImageVulnerabilityOrderField]vulnerabil "STATE": vulnerabilities.OrderByReason, "SUPPRESSED": vulnerabilities.OrderBySuppressed, "SEVERITY_SINCE": vulnerabilities.OrderBySeveritySince, + "PRIORITY": vulnerabilities.OrderByPriority, } var SortFilterWorkloadSummaries = map[VulnerabilitySummaryOrderByField]vulnerabilities.OrderByField{ - "NAME": vulnerabilities.OrderByWorkload, - "ENVIRONMENT": vulnerabilities.OrderByCluster, - "VULNERABILITY_RISK_SCORE": vulnerabilities.OrderByRiskScore, - "VULNERABILITY_SEVERITY_CRITICAL": vulnerabilities.OrderByCritical, - "VULNERABILITY_SEVERITY_HIGH": vulnerabilities.OrderByHigh, - "VULNERABILITY_SEVERITY_MEDIUM": vulnerabilities.OrderByMedium, - "VULNERABILITY_SEVERITY_LOW": vulnerabilities.OrderByLow, - "VULNERABILITY_SEVERITY_UNASSIGNED": vulnerabilities.OrderByUnassigned, + "NAME": vulnerabilities.OrderByWorkload, + "ENVIRONMENT": vulnerabilities.OrderByCluster, + "VULNERABILITY_RISK_SCORE": vulnerabilities.OrderByRiskScore, + "VULNERABILITY_SEVERITY_CRITICAL": vulnerabilities.OrderByCritical, + "VULNERABILITY_SEVERITY_HIGH": vulnerabilities.OrderByHigh, + "VULNERABILITY_SEVERITY_MEDIUM": vulnerabilities.OrderByMedium, + "VULNERABILITY_SEVERITY_LOW": vulnerabilities.OrderByLow, + "VULNERABILITY_SEVERITY_UNASSIGNED": vulnerabilities.OrderByUnassigned, + "VULNERABILITY_PRIORITY_URGENT": vulnerabilities.OrderByActNow, + "VULNERABILITY_PRIORITY_HIGH_RISK": vulnerabilities.OrderByHighRisk, + "VULNERABILITY_PRIORITY_ELEVATED_RISK": vulnerabilities.OrderByElevatedRisk, + "VULNERABILITY_PRIORITY_MONITOR": vulnerabilities.OrderByMonitor, } const ( @@ -78,6 +83,18 @@ func workloadInit() { workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_SEVERITY_UNASSIGNED", summarySorter(func(sum *ImageVulnerabilitySummary) int { return sum.Unassigned }), "NAME", "ENVIRONMENT") + workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_PRIORITY_URGENT", summarySorter(func(sum *ImageVulnerabilitySummary) int { + return sum.CountsByPriority.Urgent + }), "NAME", "ENVIRONMENT") + workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_PRIORITY_HIGH_RISK", summarySorter(func(sum *ImageVulnerabilitySummary) int { + return sum.CountsByPriority.HighRisk + }), "NAME", "ENVIRONMENT") + workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_PRIORITY_ELEVATED_RISK", summarySorter(func(sum *ImageVulnerabilitySummary) int { + return sum.CountsByPriority.ElevatedRisk + }), "NAME", "ENVIRONMENT") + workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_PRIORITY_MONITOR", summarySorter(func(sum *ImageVulnerabilitySummary) int { + return sum.CountsByPriority.Monitor + }), "NAME", "ENVIRONMENT") workload.SortFilter.RegisterConcurrentSort("HAS_SBOM", func(ctx context.Context, a workload.Workload) int { hasSBOM, err := GetImageHasSBOM(ctx, a.GetImageString()) if err != nil { diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 000f50ca4..0ef910459 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -26,8 +26,13 @@ func toImageVulnerability(v *vulnerabilities.Vulnerability) *ImageVulnerability Identifier: v.Cve.Id, Severity: ImageVulnerabilitySeverity(v.Cve.Severity.String()), CvssScore: v.GetCve().CvssScore, + EpssScore: v.GetCve().EpssScore, + EpssPercentile: v.GetCve().EpssPercentile, + HasKevEntry: v.GetCve().HasKevEntry, + KnownRansomwareUse: v.GetCve().KnownRansomwareUse, Description: description, Package: v.Package, + FixVersion: v.FixVersion, SeveritySince: severitySince, VulnerabilityDetailsLink: v.Cve.Link, } @@ -61,26 +66,11 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa wType = workload.TypeJob } - summary := &ImageVulnerabilitySummary{} - if s := w.GetVulnerabilitySummary(); s != nil { - var lastUpdated *time.Time - if ts := s.GetLastUpdated(); ts != nil { - t := ts.AsTime() - lastUpdated = &t - } - summary.Critical = int(s.Critical) - summary.High = int(s.High) - summary.Medium = int(s.Medium) - summary.Low = int(s.Low) - summary.Unassigned = int(s.Unassigned) - summary.Total = int(s.Total) - summary.RiskScore = int(s.RiskScore) - summary.LastUpdated = lastUpdated - } + v13sSummary := w.GetVulnerabilitySummary() return &WorkloadVulnerabilitySummary{ - Summary: summary, - HasSbom: w.GetSbomStatus().GetStatus() == vulnerabilities.SbomStatus_SBOM_STATUS_READY, + Summary: toImageVulnerabilitySummary(v13sSummary), + HasSbom: v13sSummary.GetHasSbom(), TeamSlug: slug.Slug(w.GetWorkload().GetNamespace()), EnvironmentName: environmentmapper.EnvironmentName(w.GetWorkload().GetCluster()), WorkloadReference: &workload.Reference{ @@ -90,14 +80,75 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa } } +func toImageVulnerabilitySummary(summary *vulnerabilities.Summary) *ImageVulnerabilitySummary { + if summary == nil { + summary = &vulnerabilities.Summary{} + } + + var lastUpdated *time.Time + if ts := summary.GetLastUpdated(); ts != nil { + t := ts.AsTime() + lastUpdated = &t + } + + return &ImageVulnerabilitySummary{ + Critical: int(summary.GetCritical()), + High: int(summary.GetHigh()), + Medium: int(summary.GetMedium()), + Low: int(summary.GetLow()), + Unassigned: int(summary.GetUnassigned()), + Total: int(summary.GetTotal()), + RiskScore: int(summary.GetRiskScore()), + LastUpdated: lastUpdated, + StaleImageTag: summary.StaleImageTag, + CountsBySeverity: ImageVulnerabilitySummaryCountsBySeverity{ + Critical: int(summary.GetCritical()), + High: int(summary.GetHigh()), + Medium: int(summary.GetMedium()), + Low: int(summary.GetLow()), + Unassigned: int(summary.GetUnassigned()), + }, + CountsByPriority: ImageVulnerabilitySummaryCountsByPriority{ + Urgent: int(summary.GetActNow()), + HighRisk: int(summary.GetHighRisk()), + ElevatedRisk: int(summary.GetElevatedRisk()), + Monitor: int(summary.GetMonitor()), + }, + } +} + func toCVE(cve *vulnerabilities.Cve) *CVE { return &CVE{ - Identifier: cve.Id, - Title: cve.Title, - Description: cve.Description, - DetailsLink: cve.Link, - CVSSScore: cve.CvssScore, - Severity: ImageVulnerabilitySeverity(cve.Severity.String()), + Identifier: cve.Id, + Title: cve.Title, + Description: cve.Description, + DetailsLink: cve.Link, + CVSSScore: cve.CvssScore, + Severity: ImageVulnerabilitySeverity(cve.Severity.String()), + Priority: cvePriorityFromV13s(cve), + EpssScore: cve.EpssScore, + EpssPercentile: cve.EpssPercentile, + HasKevEntry: cve.HasKevEntry, + KnownRansomwareUse: cve.KnownRansomwareUse, + } +} + +func cvePriorityFromV13s(cve *vulnerabilities.Cve) CVEPriority { + if cve == nil { + return CVEPriorityMonitor + } + + switch cve.GetPriority() { + case vulnerabilities.Priority_PRIORITY_ACT_NOW: + return CVEPriorityActNow + case vulnerabilities.Priority_PRIORITY_HIGH: + return CVEPriorityHigh + case vulnerabilities.Priority_PRIORITY_ELEVATED: + return CVEPriorityElevated + case vulnerabilities.Priority_PRIORITY_MONITOR: + return CVEPriorityMonitor + default: + return CVEPriorityMonitor } } @@ -117,3 +168,18 @@ func mapVulnerabilitySeverity(severity ImageVulnerabilitySeverity) vulnerabiliti return vulnerabilities.Severity_UNASSIGNED } } + +func mapCVEPriorityToPriority(p CVEPriority) vulnerabilities.Priority { + switch p { + case CVEPriorityActNow: + return vulnerabilities.Priority_PRIORITY_ACT_NOW + case CVEPriorityHigh: + return vulnerabilities.Priority_PRIORITY_HIGH + case CVEPriorityElevated: + return vulnerabilities.Priority_PRIORITY_ELEVATED + case CVEPriorityMonitor: + return vulnerabilities.Priority_PRIORITY_MONITOR + default: + return vulnerabilities.Priority_PRIORITY_UNSPECIFIED + } +} diff --git a/internal/vulnerability/transform_test.go b/internal/vulnerability/transform_test.go new file mode 100644 index 000000000..eae62eced --- /dev/null +++ b/internal/vulnerability/transform_test.go @@ -0,0 +1,43 @@ +package vulnerability + +import ( + "testing" + "time" + + "github.com/nais/v13s/pkg/api/vulnerabilities" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestToImageVulnerabilitySummary(t *testing.T) { + lastUpdated := time.Date(2026, time.June, 30, 12, 0, 0, 0, time.UTC) + staleImageTag := "previous" + + got := toImageVulnerabilitySummary(&vulnerabilities.Summary{ + Critical: 1, + High: 2, + Medium: 3, + Low: 4, + Unassigned: 5, + Total: 15, + RiskScore: 42, + ActNow: 6, + HighRisk: 7, + ElevatedRisk: 8, + Monitor: 9, + LastUpdated: timestamppb.New(lastUpdated), + StaleImageTag: &staleImageTag, + }) + + if got.CountsBySeverity != (ImageVulnerabilitySummaryCountsBySeverity{Critical: 1, High: 2, Medium: 3, Low: 4, Unassigned: 5}) { + t.Fatalf("CountsBySeverity = %#v", got.CountsBySeverity) + } + if got.CountsByPriority != (ImageVulnerabilitySummaryCountsByPriority{Urgent: 6, HighRisk: 7, ElevatedRisk: 8, Monitor: 9}) { + t.Fatalf("CountsByPriority = %#v", got.CountsByPriority) + } + if got.Total != 15 || got.RiskScore != 42 || got.StaleImageTag == nil || *got.StaleImageTag != "previous" { + t.Fatalf("summary metadata = %#v", got) + } + if got.LastUpdated == nil || !got.LastUpdated.Equal(lastUpdated) { + t.Fatalf("LastUpdated = %v, want %v", got.LastUpdated, lastUpdated) + } +}