From d11d67cfe8b2aaed17752a76e912ca8b84e977b9 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Mon, 25 May 2026 19:29:02 +0200 Subject: [PATCH 01/29] feat: add CVE priority levels to vulnerability GraphQL API and issue checker - Add priorityActNow, priorityHigh, priorityElevated, priorityMonitor fields to ImageVulnerabilitySummary model - Expose priority fields in vulnerability.graphqls and GraphQL resolvers - Add VULNERABILITY_PRIORITY_ACT_NOW and VULNERABILITY_PRIORITY_HIGH sort fields - Add ExternalIngressActNowVulnerabilityIssue type and issue checker - Map priority signals (EPSS, KEV, ransomware) via VulnerabilityPrioritySignals - Bump golang.org/x/net to v0.55.0 and golang.org/x/crypto to v0.52.0 to fix known vulnerabilities - Update v13s/pkg/api to v0.0.0-20260525171357-13563f32226d (priority_elevated, priority_monitor support) --- internal/graph/gengql/issues.generated.go | 322 ++++++++++++++++++++++ internal/graph/gengql/root_.generated.go | 73 +++++ internal/graph/gengql/schema.generated.go | 7 + internal/graph/issues.resolvers.go | 14 + internal/graph/schema/issues.graphqls | 13 + internal/issue/checker/workload_v13s.go | 77 +++++- internal/issue/model.go | 18 +- internal/issue/queries.go | 9 + internal/vulnerability/fake/fakedata.go | 68 +++-- internal/vulnerability/models.go | 21 +- internal/vulnerability/sortfilter.go | 9 + 11 files changed, 585 insertions(+), 46 deletions(-) diff --git a/internal/graph/gengql/issues.generated.go b/internal/graph/gengql/issues.generated.go index b121b8129..f1077272d 100644 --- a/internal/graph/gengql/issues.generated.go +++ b/internal/graph/gengql/issues.generated.go @@ -43,6 +43,11 @@ type DeprecatedRegistryIssueResolver interface { Workload(ctx context.Context, obj *issue.DeprecatedRegistryIssue) (workload.Workload, error) } +type ExternalIngressActNowVulnerabilityIssueResolver interface { + TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (*team.TeamEnvironment, error) + + Workload(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (workload.Workload, error) +} type ExternalIngressCriticalVulnerabilityIssueResolver interface { TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (*team.TeamEnvironment, error) @@ -607,6 +612,185 @@ func (ec *executionContext) fieldContext_DeprecatedRegistryIssue_workload(_ cont return fc, nil } +func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue_id(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressActNowVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressActNowVulnerabilityIssue_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_ExternalIngressActNowVulnerabilityIssue_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressActNowVulnerabilityIssue", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue_teamEnvironment(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressActNowVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressActNowVulnerabilityIssue_teamEnvironment(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.ExternalIngressActNowVulnerabilityIssue().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_ExternalIngressActNowVulnerabilityIssue_teamEnvironment(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ExternalIngressActNowVulnerabilityIssue", + 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) _ExternalIngressActNowVulnerabilityIssue_severity(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressActNowVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressActNowVulnerabilityIssue_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_ExternalIngressActNowVulnerabilityIssue_severity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressActNowVulnerabilityIssue", field, false, false, errors.New("field of type Severity does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue_message(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressActNowVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressActNowVulnerabilityIssue_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_ExternalIngressActNowVulnerabilityIssue_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressActNowVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue_workload(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressActNowVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressActNowVulnerabilityIssue_workload(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.ExternalIngressActNowVulnerabilityIssue().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_ExternalIngressActNowVulnerabilityIssue_workload(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ExternalIngressActNowVulnerabilityIssue", + 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) _ExternalIngressActNowVulnerabilityIssue_priorityActNow(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressActNowVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressActNowVulnerabilityIssue_priorityActNow(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.PriorityActNow, 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_ExternalIngressActNowVulnerabilityIssue_priorityActNow(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressActNowVulnerabilityIssue", field, false, false, errors.New("field of type Int does not have child fields")) +} + +func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue_ingresses(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressActNowVulnerabilityIssue) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_ExternalIngressActNowVulnerabilityIssue_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_ExternalIngressActNowVulnerabilityIssue_ingresses(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ExternalIngressActNowVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) +} + func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_id(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2812,6 +2996,13 @@ func (ec *executionContext) _Issue(ctx context.Context, sel ast.SelectionSet, ob return graphql.Null } return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, obj) + case issue.ExternalIngressActNowVulnerabilityIssue: + return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, &obj) + case *issue.ExternalIngressActNowVulnerabilityIssue: + if obj == nil { + return graphql.Null + } + return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, obj) case issue.DeprecatedRegistryIssue: return ec._DeprecatedRegistryIssue(ctx, sel, &obj) case *issue.DeprecatedRegistryIssue: @@ -3229,6 +3420,137 @@ func (ec *executionContext) _DeprecatedRegistryIssue(ctx context.Context, sel as return out } +var externalIngressActNowVulnerabilityIssueImplementors = []string{"ExternalIngressActNowVulnerabilityIssue", "Issue", "Node"} + +func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue(ctx context.Context, sel ast.SelectionSet, obj *issue.ExternalIngressActNowVulnerabilityIssue) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, externalIngressActNowVulnerabilityIssueImplementors) + + 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("ExternalIngressActNowVulnerabilityIssue") + case "id": + out.Values[i] = ec._ExternalIngressActNowVulnerabilityIssue_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._ExternalIngressActNowVulnerabilityIssue_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._ExternalIngressActNowVulnerabilityIssue_severity(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "message": + out.Values[i] = ec._ExternalIngressActNowVulnerabilityIssue_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._ExternalIngressActNowVulnerabilityIssue_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 "priorityActNow": + out.Values[i] = ec._ExternalIngressActNowVulnerabilityIssue_priorityActNow(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "ingresses": + out.Values[i] = ec._ExternalIngressActNowVulnerabilityIssue_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 externalIngressCriticalVulnerabilityIssueImplementors = []string{"ExternalIngressCriticalVulnerabilityIssue", "Issue", "Node"} func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue(ctx context.Context, sel ast.SelectionSet, obj *issue.ExternalIngressCriticalVulnerabilityIssue) graphql.Marshaler { diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 3261a9c2a..4a9f70ad7 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -81,6 +81,7 @@ type ResolverRoot interface { DeprecatedIngressIssue() DeprecatedIngressIssueResolver DeprecatedRegistryIssue() DeprecatedRegistryIssueResolver Environment() EnvironmentResolver + ExternalIngressActNowVulnerabilityIssue() ExternalIngressActNowVulnerabilityIssueResolver ExternalIngressCriticalVulnerabilityIssue() ExternalIngressCriticalVulnerabilityIssueResolver FailedSynchronizationIssue() FailedSynchronizationIssueResolver Ingress() IngressResolver @@ -935,6 +936,16 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + ExternalIngressActNowVulnerabilityIssue struct { + ID func(childComplexity int) int + Ingresses func(childComplexity int) int + Message func(childComplexity int) int + PriorityActNow func(childComplexity int) int + Severity func(childComplexity int) int + TeamEnvironment func(childComplexity int) int + Workload func(childComplexity int) int + } + ExternalIngressCriticalVulnerabilityIssue struct { CvssScore func(childComplexity int) int ID func(childComplexity int) int @@ -6741,6 +6752,55 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.EnvironmentEdge.Node(childComplexity), true + case "ExternalIngressActNowVulnerabilityIssue.id": + if e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.ID == nil { + break + } + + return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.ID(childComplexity), true + + case "ExternalIngressActNowVulnerabilityIssue.ingresses": + if e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Ingresses == nil { + break + } + + return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Ingresses(childComplexity), true + + case "ExternalIngressActNowVulnerabilityIssue.message": + if e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Message == nil { + break + } + + return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Message(childComplexity), true + + case "ExternalIngressActNowVulnerabilityIssue.priorityActNow": + if e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.PriorityActNow == nil { + break + } + + return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.PriorityActNow(childComplexity), true + + case "ExternalIngressActNowVulnerabilityIssue.severity": + if e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Severity == nil { + break + } + + return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Severity(childComplexity), true + + case "ExternalIngressActNowVulnerabilityIssue.teamEnvironment": + if e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.TeamEnvironment == nil { + break + } + + return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.TeamEnvironment(childComplexity), true + + case "ExternalIngressActNowVulnerabilityIssue.workload": + if e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Workload == nil { + break + } + + return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Workload(childComplexity), true + case "ExternalIngressCriticalVulnerabilityIssue.cvssScore": if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.CvssScore == nil { break @@ -23227,6 +23287,7 @@ enum IssueType { MISSING_SBOM VULNERABLE_IMAGE EXTERNAL_INGRESS_CRITICAL_VULNERABILITY + EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." APPLICATION_RESTART_LOOP @@ -23254,6 +23315,18 @@ type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { ingresses: [String!]! } +"Raised when a workload with external ingresses has one or more ACT_NOW priority vulnerabilities." +type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { + id: ID! + teamEnvironment: TeamEnvironment! + severity: Severity! + message: String! + + workload: Workload! + priorityActNow: Int! + ingresses: [String!]! +} + type MissingSbomIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! diff --git a/internal/graph/gengql/schema.generated.go b/internal/graph/gengql/schema.generated.go index 80345a50b..1ee65c682 100644 --- a/internal/graph/gengql/schema.generated.go +++ b/internal/graph/gengql/schema.generated.go @@ -6516,6 +6516,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, obj) + case issue.ExternalIngressActNowVulnerabilityIssue: + return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, &obj) + case *issue.ExternalIngressActNowVulnerabilityIssue: + if obj == nil { + return graphql.Null + } + return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, obj) case issue.DeprecatedRegistryIssue: return ec._DeprecatedRegistryIssue(ctx, sel, &obj) case *issue.DeprecatedRegistryIssue: diff --git a/internal/graph/issues.resolvers.go b/internal/graph/issues.resolvers.go index 0301eec5e..a0fa5f0d2 100644 --- a/internal/graph/issues.resolvers.go +++ b/internal/graph/issues.resolvers.go @@ -2,6 +2,7 @@ package graph import ( "context" + "fmt" "github.com/nais/api/internal/graph/gengql" "github.com/nais/api/internal/graph/pagination" @@ -40,6 +41,14 @@ func (r *deprecatedRegistryIssueResolver) Workload(ctx context.Context, obj *iss return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) } +func (r *externalIngressActNowVulnerabilityIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (*team.TeamEnvironment, error) { + panic(fmt.Errorf("not implemented: TeamEnvironment - teamEnvironment")) +} + +func (r *externalIngressActNowVulnerabilityIssueResolver) Workload(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (workload.Workload, error) { + panic(fmt.Errorf("not implemented: Workload - workload")) +} + func (r *externalIngressCriticalVulnerabilityIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (*team.TeamEnvironment, error) { return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) } @@ -157,6 +166,10 @@ func (r *Resolver) DeprecatedRegistryIssue() gengql.DeprecatedRegistryIssueResol return &deprecatedRegistryIssueResolver{r} } +func (r *Resolver) ExternalIngressActNowVulnerabilityIssue() gengql.ExternalIngressActNowVulnerabilityIssueResolver { + return &externalIngressActNowVulnerabilityIssueResolver{r} +} + func (r *Resolver) ExternalIngressCriticalVulnerabilityIssue() gengql.ExternalIngressCriticalVulnerabilityIssueResolver { return &externalIngressCriticalVulnerabilityIssueResolver{r} } @@ -207,6 +220,7 @@ type ( applicationRestartLoopIssueResolver struct{ *Resolver } deprecatedIngressIssueResolver struct{ *Resolver } deprecatedRegistryIssueResolver struct{ *Resolver } + externalIngressActNowVulnerabilityIssueResolver struct{ *Resolver } externalIngressCriticalVulnerabilityIssueResolver struct{ *Resolver } failedSynchronizationIssueResolver struct{ *Resolver } invalidSpecIssueResolver struct{ *Resolver } diff --git a/internal/graph/schema/issues.graphqls b/internal/graph/schema/issues.graphqls index 391159923..2932e6e7f 100644 --- a/internal/graph/schema/issues.graphqls +++ b/internal/graph/schema/issues.graphqls @@ -164,6 +164,7 @@ enum IssueType { MISSING_SBOM VULNERABLE_IMAGE EXTERNAL_INGRESS_CRITICAL_VULNERABILITY + EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." APPLICATION_RESTART_LOOP @@ -191,6 +192,18 @@ type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { ingresses: [String!]! } +"Raised when a workload with external ingresses has one or more ACT_NOW priority vulnerabilities." +type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { + id: ID! + teamEnvironment: TeamEnvironment! + severity: Severity! + message: String! + + workload: Workload! + priorityActNow: Int! + ingresses: [String!]! +} + type MissingSbomIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 1fc65db5d..95c925762 100644 --- a/internal/issue/checker/workload_v13s.go +++ b/internal/issue/checker/workload_v13s.go @@ -35,11 +35,13 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... Cluster: "dev-gcp", Type: "app", ImageName: "vulnerable-image", - ImageTag: "tag1", + ImageTag: "tag1", }, VulnerabilitySummary: &vulnerabilities.Summary{ - Critical: 5, - RiskScore: 250, + Critical: 5, + RiskScore: 250, + PriorityActNow: 2, + PriorityHigh: 3, }, SbomStatus: &vulnerabilities.SbomStatusInfo{ Status: vulnerabilities.SbomStatus_SBOM_STATUS_READY, @@ -67,11 +69,13 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... Cluster: "dev-gcp", Type: "app", ImageName: "vulnerable-image", - ImageTag: "tag1", + ImageTag: "tag1", }, VulnerabilitySummary: &vulnerabilities.Summary{ - Critical: 5, - RiskScore: 250, + Critical: 5, + RiskScore: 250, + PriorityActNow: 2, + PriorityHigh: 3, }, SbomStatus: &vulnerabilities.SbomStatusInfo{ Status: vulnerabilities.SbomStatus_SBOM_STATUS_READY, @@ -181,23 +185,28 @@ 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.PriorityActNow > 0 || summary.PriorityHigh > 0) { + severity := issue.SeverityWarning + if summary.PriorityActNow > 0 { + severity = issue.SeverityCritical + } 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: severity, Message: fmt.Sprintf( - "Image '%s' has %d critical vulnerabilities and a risk score of %d", + "Image '%s' has %d ACT_NOW and %d HIGH priority vulnerabilities", node.Workload.ImageName, - node.VulnerabilitySummary.Critical, - node.VulnerabilitySummary.RiskScore, + summary.PriorityActNow, + summary.PriorityHigh, ), IssueDetails: issue.VulnerableImageIssueDetails{ - Critical: int(node.VulnerabilitySummary.Critical), - RiskScore: int(node.VulnerabilitySummary.RiskScore), + Critical: int(summary.Critical), + RiskScore: int(summary.RiskScore), }, }) } @@ -280,6 +289,48 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { }) } + seenActNow := map[string]struct{}{} + for _, node := range resp.GetNodes() { + workloadType, ok := mapType(node.Workload.GetType()) + if !ok || workloadType != issue.ResourceTypeApplication { + continue + } + + if node.VulnerabilitySummary == nil || node.VulnerabilitySummary.PriorityActNow == 0 { + continue + } + + env := environmentmapper.EnvironmentName(node.Workload.GetCluster()) + key := workloadKey(env, node.Workload.GetNamespace(), node.Workload.GetName()) + if _, exists := seenActNow[key]; exists { + continue + } + + externalIngresses := externalIngressesByWorkload[key] + if len(externalIngresses) == 0 { + continue + } + seenActNow[key] = struct{}{} + + ret = append(ret, &Issue{ + IssueType: issue.IssueTypeExternalIngressActNowVulnerability, + ResourceType: workloadType, + ResourceName: node.Workload.GetName(), + Team: node.Workload.GetNamespace(), + Env: env, + Severity: issue.SeverityCritical, + Message: fmt.Sprintf( + "Workload with external ingresses %s has %d ACT_NOW priority vulnerabilities", + strings.Join(externalIngresses, ", "), + node.VulnerabilitySummary.PriorityActNow, + ), + IssueDetails: issue.ExternalIngressActNowVulnerabilityIssueDetails{ + PriorityActNow: int(node.VulnerabilitySummary.PriorityActNow), + Ingresses: externalIngresses, + }, + }) + } + return ret } diff --git a/internal/issue/model.go b/internal/issue/model.go index 315ff3d69..500ca5c4a 100644 --- a/internal/issue/model.go +++ b/internal/issue/model.go @@ -188,6 +188,11 @@ type ExternalIngressCriticalVulnerabilityIssueDetails struct { Ingresses []string `json:"ingresses"` } +type ExternalIngressActNowVulnerabilityIssueDetails struct { + PriorityActNow int `json:"priorityActNow"` + Ingresses []string `json:"ingresses"` +} + type IssueType string const ( @@ -204,6 +209,7 @@ const ( IssueTypeVulnerableImage IssueType = "VULNERABLE_IMAGE" IssueTypeMissingSBOM IssueType = "MISSING_SBOM" IssueTypeExternalIngressCriticalVulnerability IssueType = "EXTERNAL_INGRESS_CRITICAL_VULNERABILITY" + IssueTypeExternalIngressActNowVulnerability IssueType = "EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY" IssueTypeUnleashReleaseChannel IssueType = "UNLEASH_RELEASE_CHANNEL" IssueTypeApplicationRestartLoop IssueType = "APPLICATION_RESTART_LOOP" ) @@ -222,13 +228,14 @@ var AllIssueType = []IssueType{ IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressCriticalVulnerability, + IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop, } func (e IssueType) IsValid() bool { switch e { - case IssueTypeOpenSearch, IssueTypeValkey, IssueTypeSqlInstanceState, IssueTypeSqlInstanceVersion, IssueTypeDeprecatedIngress, IssueTypeDeprecatedRegistry, IssueTypeNoRunningInstances, IssueTypeLastRunFailed, IssueTypeInvalidSpec, IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressCriticalVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop: + case IssueTypeOpenSearch, IssueTypeValkey, IssueTypeSqlInstanceState, IssueTypeSqlInstanceVersion, IssueTypeDeprecatedIngress, IssueTypeDeprecatedRegistry, IssueTypeNoRunningInstances, IssueTypeLastRunFailed, IssueTypeInvalidSpec, IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressCriticalVulnerability, IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop: return true } return false @@ -393,6 +400,15 @@ func (ExternalIngressCriticalVulnerabilityIssue) IsIssue() {} func (ExternalIngressCriticalVulnerabilityIssue) IsNode() {} +type ExternalIngressActNowVulnerabilityIssue struct { + Base + ExternalIngressActNowVulnerabilityIssueDetails +} + +func (ExternalIngressActNowVulnerabilityIssue) IsIssue() {} + +func (ExternalIngressActNowVulnerabilityIssue) IsNode() {} + type UnleashReleaseChannelIssueDetails struct { ChannelName string `json:"channelName"` MajorVersion int `json:"majorVersion"` diff --git a/internal/issue/queries.go b/internal/issue/queries.go index 4af566436..b445b6878 100644 --- a/internal/issue/queries.go +++ b/internal/issue/queries.go @@ -182,6 +182,15 @@ func convert(issue *issuesql.Issue) (Issue, error) { Base: base, ExternalIngressCriticalVulnerabilityIssueDetails: *d, }, nil + case IssueTypeExternalIngressActNowVulnerability: + d, err := unmarshal[ExternalIngressActNowVulnerabilityIssueDetails](issue.IssueDetails) + if err != nil { + return nil, err + } + return &ExternalIngressActNowVulnerabilityIssue{ + Base: base, + ExternalIngressActNowVulnerabilityIssueDetails: *d, + }, nil case IssueTypeUnleashReleaseChannel: d, err := unmarshal[UnleashReleaseChannelIssueDetails](issue.IssueDetails) if err != nil { diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index eb9f3ff0a..e609af606 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -79,15 +79,19 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact imageName := parts[0] imageTag := parts[1] 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()), + PriorityActNow: vulnFactor, + PriorityHigh: vulnFactor * 2, + PriorityElevated: vulnFactor * 3, + PriorityMonitor: vulnFactor * 4, } if name == "no-errors" { @@ -120,38 +124,56 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact func createVulnerabilities(w *vulnerabilities.WorkloadSummary) []*vulnerabilities.Vulnerability { findings := make([]*vulnerabilities.Vulnerability, 0) + priorities := []vulnerabilities.Priority{ + vulnerabilities.Priority_PRIORITY_ACT_NOW, + vulnerabilities.Priority_PRIORITY_HIGH, + vulnerabilities.Priority_PRIORITY_ELEVATED, + vulnerabilities.Priority_PRIORITY_MONITOR, + } + idx := 0 + nextPriority := func() vulnerabilities.Priority { + p := priorities[idx%len(priorities)] + idx++ + return p + } + epssScore := 0.85 + epssPercentile := 97.3 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), nextPriority(), &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), nextPriority(), 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), nextPriority(), 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), nextPriority(), 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), nextPriority(), nil, nil, false, false)) } return findings } -func createVulnerability(severity vulnerabilities.Severity, componentName string) *vulnerabilities.Vulnerability { +func createVulnerability(severity vulnerabilities.Severity, componentName string, priority vulnerabilities.Priority, 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, + Priority: priority, + 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/models.go b/internal/vulnerability/models.go index a05217e76..2517af7f8 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -71,15 +71,18 @@ 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"` + 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"` + PriorityActNow int `json:"priorityActNow"` + PriorityHigh int `json:"priorityHigh"` + PriorityElevated int `json:"priorityElevated"` + PriorityMonitor int `json:"priorityMonitor"` } type ImageVulnerabilityOrderField string diff --git a/internal/vulnerability/sortfilter.go b/internal/vulnerability/sortfilter.go index cad3014d3..49625cb64 100644 --- a/internal/vulnerability/sortfilter.go +++ b/internal/vulnerability/sortfilter.go @@ -16,6 +16,7 @@ var SortFilterImageVulnerabilities = map[ImageVulnerabilityOrderField]vulnerabil "STATE": vulnerabilities.OrderByReason, "SUPPRESSED": vulnerabilities.OrderBySuppressed, "SEVERITY_SINCE": vulnerabilities.OrderBySeveritySince, + "PRIORITY": vulnerabilities.OrderByPriority, } var SortFilterWorkloadSummaries = map[VulnerabilitySummaryOrderByField]vulnerabilities.OrderByField{ @@ -27,6 +28,8 @@ var SortFilterWorkloadSummaries = map[VulnerabilitySummaryOrderByField]vulnerabi "VULNERABILITY_SEVERITY_MEDIUM": vulnerabilities.OrderByMedium, "VULNERABILITY_SEVERITY_LOW": vulnerabilities.OrderByLow, "VULNERABILITY_SEVERITY_UNASSIGNED": vulnerabilities.OrderByUnassigned, + "VULNERABILITY_PRIORITY_ACT_NOW": vulnerabilities.OrderByPriorityActNow, + "VULNERABILITY_PRIORITY_HIGH": vulnerabilities.OrderByPriorityHigh, } const ( @@ -78,6 +81,12 @@ func workloadInit() { workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_SEVERITY_UNASSIGNED", summarySorter(func(sum *ImageVulnerabilitySummary) int { return sum.Unassigned }), "NAME", "ENVIRONMENT") + workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_PRIORITY_ACT_NOW", summarySorter(func(sum *ImageVulnerabilitySummary) int { + return sum.PriorityActNow + }), "NAME", "ENVIRONMENT") + workload.SortFilter.RegisterConcurrentSort("VULNERABILITY_PRIORITY_HIGH", summarySorter(func(sum *ImageVulnerabilitySummary) int { + return sum.PriorityHigh + }), "NAME", "ENVIRONMENT") workload.SortFilter.RegisterConcurrentSort("HAS_SBOM", func(ctx context.Context, a workload.Workload) int { hasSBOM, err := GetImageHasSBOM(ctx, a.GetImageString()) if err != nil { From 33670949e2a362ebf66499ffec39cac871630e47 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Thu, 28 May 2026 13:53:39 +0200 Subject: [PATCH 02/29] feat(vulnerability): expose CVE priority, EPSS, KEV fields in GraphQL; fix ExternalIngressActNow resolver stubs; add priority sort fields --- internal/graph/gengql/root_.generated.go | 173 +++++++++- .../graph/gengql/vulnerability.generated.go | 310 +++++++++++++++++- internal/graph/issues.resolvers.go | 5 +- internal/graph/schema/vulnerability.graphqls | 48 +++ internal/graph/vulnerability.resolvers.go | 10 + internal/issue/checker/workload_v13s.go | 4 +- internal/vulnerability/models.go | 67 +++- internal/vulnerability/queries.go | 2 + internal/vulnerability/transform.go | 30 +- 9 files changed, 602 insertions(+), 47 deletions(-) diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 4a9f70ad7..b89788be2 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -84,6 +84,7 @@ type ResolverRoot interface { ExternalIngressActNowVulnerabilityIssue() ExternalIngressActNowVulnerabilityIssueResolver ExternalIngressCriticalVulnerabilityIssue() ExternalIngressCriticalVulnerabilityIssueResolver FailedSynchronizationIssue() FailedSynchronizationIssueResolver + ImageVulnerabilitySummary() ImageVulnerabilitySummaryResolver Ingress() IngressResolver IngressMetrics() IngressMetricsResolver InstanceGroup() InstanceGroupResolver @@ -524,14 +525,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 { @@ -1075,15 +1081,19 @@ 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 + 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 + PriorityActNow func(childComplexity int) int + PriorityElevated func(childComplexity int) int + PriorityHigh func(childComplexity int) int + PriorityMonitor func(childComplexity int) int + RiskScore func(childComplexity int) int + StaleImageTag func(childComplexity int) int + Total func(childComplexity int) int + Unassigned func(childComplexity int) int } ImageVulnerabilitySuppression struct { @@ -5250,6 +5260,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 @@ -5264,6 +5295,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 @@ -7333,6 +7378,34 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.Medium(childComplexity), true + case "ImageVulnerabilitySummary.priorityActNow": + if e.ComplexityRoot.ImageVulnerabilitySummary.PriorityActNow == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.PriorityActNow(childComplexity), true + + case "ImageVulnerabilitySummary.priorityElevated": + if e.ComplexityRoot.ImageVulnerabilitySummary.PriorityElevated == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.PriorityElevated(childComplexity), true + + case "ImageVulnerabilitySummary.priorityHigh": + if e.ComplexityRoot.ImageVulnerabilitySummary.PriorityHigh == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.PriorityHigh(childComplexity), true + + case "ImageVulnerabilitySummary.priorityMonitor": + if e.ComplexityRoot.ImageVulnerabilitySummary.PriorityMonitor == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.PriorityMonitor(childComplexity), true + case "ImageVulnerabilitySummary.riskScore": if e.ComplexityRoot.ImageVulnerabilitySummary.RiskScore == nil { break @@ -31321,6 +31394,7 @@ enum CVEOrderField { SEVERITY CVSS_SCORE AFFECTED_WORKLOADS_COUNT + PRIORITY } extend interface Workload { @@ -31556,6 +31630,18 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! + "Number of vulnerabilities with priority ACT_NOW." + priorityActNow: Int! + + "Number of vulnerabilities with priority HIGH." + priorityHigh: Int! + + "Number of vulnerabilities with priority ELEVATED." + priorityElevated: Int! + + "Number of vulnerabilities with priority MONITOR." + priorityMonitor: Int! + "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -31646,6 +31732,17 @@ type ImageVulnerability implements Node { cvssScore: Float } +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 { "The globally unique ID of the CVE." id: ID! @@ -31668,6 +31765,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." @@ -31778,6 +31890,7 @@ enum ImageVulnerabilityOrderField { PACKAGE STATE SUPPRESSED + PRIORITY } type WorkloadVulnerabilitySummary implements Node { @@ -31843,6 +31956,14 @@ enum VulnerabilitySummaryOrderByField { Order by vulnerability severity unassigned" """ VULNERABILITY_SEVERITY_UNASSIGNED + """ + Order by priority ACT_NOW count" + """ + VULNERABILITY_PRIORITY_ACT_NOW + """ + Order by priority HIGH count" + """ + VULNERABILITY_PRIORITY_HIGH } type TenantVulnerabilitySummary { @@ -33134,6 +33255,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) } @@ -33946,6 +34077,14 @@ func (ec *executionContext) childFields_ImageVulnerabilitySummary(ctx context.Co return ec.fieldContext_ImageVulnerabilitySummary_critical(ctx, field) case "unassigned": return ec.fieldContext_ImageVulnerabilitySummary_unassigned(ctx, field) + case "priorityActNow": + return ec.fieldContext_ImageVulnerabilitySummary_priorityActNow(ctx, field) + case "priorityHigh": + return ec.fieldContext_ImageVulnerabilitySummary_priorityHigh(ctx, field) + case "priorityElevated": + return ec.fieldContext_ImageVulnerabilitySummary_priorityElevated(ctx, field) + case "priorityMonitor": + return ec.fieldContext_ImageVulnerabilitySummary_priorityMonitor(ctx, field) case "lastUpdated": return ec.fieldContext_ImageVulnerabilitySummary_lastUpdated(ctx, field) case "staleImageTag": diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index c2a7a9a1c..c80f86fbe 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -32,6 +32,9 @@ type ContainerImageSBOMResolver interface { type ContainerImageWorkloadReferenceResolver interface { Workload(ctx context.Context, obj *vulnerability.ContainerImageWorkloadReference) (workload.Workload, error) } +type ImageVulnerabilitySummaryResolver interface { + StaleImageTag(ctx context.Context, obj *vulnerability.ImageVulnerabilitySummary) (*string, error) +} type TeamVulnerabilitySummaryResolver interface { RiskScoreTrend(ctx context.Context, obj *vulnerability.TeamVulnerabilitySummary) (vulnerability.TeamVulnerabilityRiskScoreTrend, error) } @@ -258,6 +261,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, @@ -1320,6 +1438,98 @@ func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_unassigned(_ return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) } +func (ec *executionContext) _ImageVulnerabilitySummary_priorityActNow(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_priorityActNow(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.PriorityActNow, 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_priorityActNow(_ 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_priorityHigh(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_priorityHigh(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.PriorityHigh, 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_priorityHigh(_ 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_priorityElevated(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_priorityElevated(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.PriorityElevated, 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_priorityElevated(_ 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_priorityMonitor(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_priorityMonitor(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.PriorityMonitor, 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_priorityMonitor(_ 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_lastUpdated(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1352,7 +1562,7 @@ func (ec *executionContext) _ImageVulnerabilitySummary_staleImageTag(ctx context return ec.fieldContext_ImageVulnerabilitySummary_staleImageTag(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.StaleImageTag, nil + return ec.Resolvers.ImageVulnerabilitySummary().StaleImageTag(ctx, obj) }, nil, func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { @@ -1363,7 +1573,7 @@ func (ec *executionContext) _ImageVulnerabilitySummary_staleImageTag(ctx context ) } 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")) + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, true, true, errors.New("field of type String does not have child fields")) } func (ec *executionContext) _ImageVulnerabilitySuppression_state(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySuppression) (ret graphql.Marshaler) { @@ -3243,6 +3453,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 @@ -3926,42 +4155,93 @@ func (ec *executionContext) _ImageVulnerabilitySummary(ctx context.Context, sel case "total": out.Values[i] = ec._ImageVulnerabilitySummary_total(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "riskScore": out.Values[i] = ec._ImageVulnerabilitySummary_riskScore(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "low": out.Values[i] = ec._ImageVulnerabilitySummary_low(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "medium": out.Values[i] = ec._ImageVulnerabilitySummary_medium(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "high": out.Values[i] = ec._ImageVulnerabilitySummary_high(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "critical": out.Values[i] = ec._ImageVulnerabilitySummary_critical(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "unassigned": out.Values[i] = ec._ImageVulnerabilitySummary_unassigned(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) + } + case "priorityActNow": + out.Values[i] = ec._ImageVulnerabilitySummary_priorityActNow(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "priorityHigh": + out.Values[i] = ec._ImageVulnerabilitySummary_priorityHigh(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "priorityElevated": + out.Values[i] = ec._ImageVulnerabilitySummary_priorityElevated(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "priorityMonitor": + out.Values[i] = ec._ImageVulnerabilitySummary_priorityMonitor(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) } case "lastUpdated": out.Values[i] = ec._ImageVulnerabilitySummary_lastUpdated(ctx, field, obj) case "staleImageTag": - out.Values[i] = ec._ImageVulnerabilitySummary_staleImageTag(ctx, field, obj) + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._ImageVulnerabilitySummary_staleImageTag(ctx, field, obj) + 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) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -4879,6 +5159,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) } diff --git a/internal/graph/issues.resolvers.go b/internal/graph/issues.resolvers.go index a0fa5f0d2..c55a111de 100644 --- a/internal/graph/issues.resolvers.go +++ b/internal/graph/issues.resolvers.go @@ -2,7 +2,6 @@ package graph import ( "context" - "fmt" "github.com/nais/api/internal/graph/gengql" "github.com/nais/api/internal/graph/pagination" @@ -42,11 +41,11 @@ func (r *deprecatedRegistryIssueResolver) Workload(ctx context.Context, obj *iss } func (r *externalIngressActNowVulnerabilityIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (*team.TeamEnvironment, error) { - panic(fmt.Errorf("not implemented: TeamEnvironment - teamEnvironment")) + return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) } func (r *externalIngressActNowVulnerabilityIssueResolver) Workload(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (workload.Workload, error) { - panic(fmt.Errorf("not implemented: Workload - workload")) + return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) } func (r *externalIngressCriticalVulnerabilityIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (*team.TeamEnvironment, error) { diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index 37645d575..fd8ef3a19 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -55,6 +55,7 @@ enum CVEOrderField { SEVERITY CVSS_SCORE AFFECTED_WORKLOADS_COUNT + PRIORITY } extend interface Workload { @@ -290,6 +291,18 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! + "Number of vulnerabilities with priority ACT_NOW." + priorityActNow: Int! + + "Number of vulnerabilities with priority HIGH." + priorityHigh: Int! + + "Number of vulnerabilities with priority ELEVATED." + priorityElevated: Int! + + "Number of vulnerabilities with priority MONITOR." + priorityMonitor: Int! + "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -380,6 +393,17 @@ type ImageVulnerability implements Node { cvssScore: Float } +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 { "The globally unique ID of the CVE." id: ID! @@ -402,6 +426,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." @@ -512,6 +551,7 @@ enum ImageVulnerabilityOrderField { PACKAGE STATE SUPPRESSED + PRIORITY } type WorkloadVulnerabilitySummary implements Node { @@ -577,6 +617,14 @@ enum VulnerabilitySummaryOrderByField { Order by vulnerability severity unassigned" """ VULNERABILITY_SEVERITY_UNASSIGNED + """ + Order by priority ACT_NOW count" + """ + VULNERABILITY_PRIORITY_ACT_NOW + """ + Order by priority HIGH count" + """ + VULNERABILITY_PRIORITY_HIGH } type TenantVulnerabilitySummary { diff --git a/internal/graph/vulnerability.resolvers.go b/internal/graph/vulnerability.resolvers.go index fbc3c706e..7529d8d23 100644 --- a/internal/graph/vulnerability.resolvers.go +++ b/internal/graph/vulnerability.resolvers.go @@ -2,6 +2,7 @@ package graph import ( "context" + "fmt" "time" "github.com/nais/api/internal/auth/authz" @@ -80,6 +81,10 @@ func (r *containerImageWorkloadReferenceResolver) Workload(ctx context.Context, return getWorkload(ctx, obj.Reference, obj.TeamSlug, environmentmapper.EnvironmentName(obj.EnvironmentName)) } +func (r *imageVulnerabilitySummaryResolver) StaleImageTag(ctx context.Context, obj *vulnerability.ImageVulnerabilitySummary) (*string, error) { + panic(fmt.Errorf("not implemented: StaleImageTag - staleImageTag")) +} + func (r *jobResolver) ImageVulnerabilityHistory(ctx context.Context, obj *job.Job, from scalar.Date) (*vulnerability.ImageVulnerabilityHistory, error) { return vulnerability.GetWorkloadVulnerabilityHistoryForWorkload(ctx, obj, from.Time()) } @@ -173,6 +178,10 @@ func (r *Resolver) ContainerImageWorkloadReference() gengql.ContainerImageWorklo return &containerImageWorkloadReferenceResolver{r} } +func (r *Resolver) ImageVulnerabilitySummary() gengql.ImageVulnerabilitySummaryResolver { + return &imageVulnerabilitySummaryResolver{r} +} + func (r *Resolver) TeamVulnerabilitySummary() gengql.TeamVulnerabilitySummaryResolver { return &teamVulnerabilitySummaryResolver{r} } @@ -185,6 +194,7 @@ type ( cVEResolver struct{ *Resolver } containerImageSBOMResolver struct{ *Resolver } containerImageWorkloadReferenceResolver struct{ *Resolver } + imageVulnerabilitySummaryResolver struct{ *Resolver } teamVulnerabilitySummaryResolver struct{ *Resolver } workloadVulnerabilitySummaryResolver struct{ *Resolver } ) diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 95c925762..827a8d994 100644 --- a/internal/issue/checker/workload_v13s.go +++ b/internal/issue/checker/workload_v13s.go @@ -35,7 +35,7 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... Cluster: "dev-gcp", Type: "app", ImageName: "vulnerable-image", - ImageTag: "tag1", + ImageTag: "tag1", }, VulnerabilitySummary: &vulnerabilities.Summary{ Critical: 5, @@ -69,7 +69,7 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... Cluster: "dev-gcp", Type: "app", ImageName: "vulnerable-image", - ImageTag: "tag1", + ImageTag: "tag1", }, VulnerabilitySummary: &vulnerabilities.Summary{ Critical: 5, diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index 2517af7f8..982781c97 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -225,6 +225,8 @@ const ( VulnerabilitySummaryOrderByFieldVulnerabilitySeverityLow VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_LOW" VulnerabilitySummaryOrderByFieldVulnerabilitySeverityUnassigned VulnerabilitySummaryOrderByField = "VULNERABILITY_SEVERITY_UNASSIGNED" VulnerabilitySummaryOrderByFieldVulnerabilityLastScanned VulnerabilitySummaryOrderByField = "VULNERABILITY_LAST_SCANNED" + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityActNow VulnerabilitySummaryOrderByField = "VULNERABILITY_PRIORITY_ACT_NOW" + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityHigh VulnerabilitySummaryOrderByField = "VULNERABILITY_PRIORITY_HIGH" ) var AllVulnerabilitySummaryOrderByField = []VulnerabilitySummaryOrderByField{ @@ -237,11 +239,13 @@ var AllVulnerabilitySummaryOrderByField = []VulnerabilitySummaryOrderByField{ VulnerabilitySummaryOrderByFieldVulnerabilitySeverityLow, VulnerabilitySummaryOrderByFieldVulnerabilitySeverityUnassigned, VulnerabilitySummaryOrderByFieldVulnerabilityLastScanned, + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityActNow, + VulnerabilitySummaryOrderByFieldVulnerabilityPriorityHigh, } 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, VulnerabilitySummaryOrderByFieldVulnerabilityPriorityActNow, VulnerabilitySummaryOrderByFieldVulnerabilityPriorityHigh: return true } return false @@ -416,14 +420,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. @@ -480,6 +527,7 @@ const ( CVEOrderFieldSeverity CVEOrderField = "SEVERITY" CVEOrderFieldCVSSScore CVEOrderField = "CVSS_SCORE" CVEOrderFieldAffectedWorkloadsCount CVEOrderField = "AFFECTED_WORKLOADS_COUNT" + CVEOrderFieldPriority CVEOrderField = "PRIORITY" ) var AllCVEOrderField = []CVEOrderField{ @@ -487,11 +535,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 86992af64..8ab7e1859 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -655,6 +655,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 } diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 000f50ca4..a9a318d42 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -92,12 +92,30 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa 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: parseCVEPriority(cve.Priority), + EpssScore: cve.EpssScore, + EpssPercentile: cve.EpssPercentile, + HasKevEntry: cve.HasKevEntry, + KnownRansomwareUse: cve.KnownRansomwareUse, + } +} + +func parseCVEPriority(p vulnerabilities.Priority) CVEPriority { + switch p { + case vulnerabilities.Priority_PRIORITY_ACT_NOW: + return CVEPriorityActNow + case vulnerabilities.Priority_PRIORITY_HIGH: + return CVEPriorityHigh + case vulnerabilities.Priority_PRIORITY_ELEVATED: + return CVEPriorityElevated + default: + return CVEPriorityMonitor } } From ee0b576facca1de20e270db389f5532129ae1765 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Thu, 28 May 2026 14:19:23 +0200 Subject: [PATCH 03/29] chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main) --- go.mod | 6 +++++- go.sum | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0dd2b21b6..c86ba9bd6 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-20260528121134-739c7136ac8e 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 @@ -80,7 +80,11 @@ require ( golang.org/x/text v0.37.0 golang.org/x/tools v0.44.0 google.golang.org/api v0.280.0 +<<<<<<< HEAD google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 +======= + google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 +>>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 k8s.io/api v0.35.1 diff --git a/go.sum b/go.sum index 08b8ab991..26c3952e1 100644 --- a/go.sum +++ b/go.sum @@ -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-20260528121134-739c7136ac8e h1:7fut4nxp6NlX7xS5SnwLkdyAhWanqmcpuClssn/cT8Q= +github.com/nais/v13s/pkg/api v0.0.0-20260528121134-739c7136ac8e/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= 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= @@ -1416,6 +1416,11 @@ 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= +<<<<<<< HEAD +======= +google.golang.org/api v0.277.0 h1:HJfyJUiNeBBUMai7ez8u14wkp/gH/I4wpGbbO9o+cSk= +google.golang.org/api v0.277.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= +>>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) 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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1428,8 +1433,15 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +<<<<<<< HEAD google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= +======= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +>>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1438,6 +1450,11 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +<<<<<<< HEAD +======= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +>>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= From b4b3aff2f05c708aca0ef40e253cca891abb0ced Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Thu, 28 May 2026 16:21:18 +0200 Subject: [PATCH 04/29] =?UTF-8?q?refactor:=20simplify=20toWorkloadVulnerab?= =?UTF-8?q?ilitySummary=20=E2=80=94=20v13s=20now=20zeroes=20counts=20for?= =?UTF-8?q?=20terminal=20states?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/vulnerability/transform.go | 41 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index a9a318d42..6b60cb8e6 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -61,26 +61,35 @@ 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() + if v13sSummary == nil { + v13sSummary = &vulnerabilities.Summary{} + } + + var lastUpdated *time.Time + if ts := v13sSummary.GetLastUpdated(); ts != nil { + t := ts.AsTime() + lastUpdated = &t + } + + summary := &ImageVulnerabilitySummary{ + Critical: int(v13sSummary.Critical), + High: int(v13sSummary.High), + Medium: int(v13sSummary.Medium), + Low: int(v13sSummary.Low), + Unassigned: int(v13sSummary.Unassigned), + Total: int(v13sSummary.Total), + RiskScore: int(v13sSummary.RiskScore), + LastUpdated: lastUpdated, + PriorityActNow: int(v13sSummary.PriorityActNow), + PriorityHigh: int(v13sSummary.PriorityHigh), + PriorityElevated: int(v13sSummary.PriorityElevated), + PriorityMonitor: int(v13sSummary.PriorityMonitor), } return &WorkloadVulnerabilitySummary{ Summary: summary, - HasSbom: w.GetSbomStatus().GetStatus() == vulnerabilities.SbomStatus_SBOM_STATUS_READY, + HasSbom: v13sSummary.GetHasSbom(), TeamSlug: slug.Slug(w.GetWorkload().GetNamespace()), EnvironmentName: environmentmapper.EnvironmentName(w.GetWorkload().GetCluster()), WorkloadReference: &workload.Reference{ From 465f96ff6de207801d419029ee939c7b611930d4 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 2 Jun 2026 09:43:29 +0200 Subject: [PATCH 05/29] fix: resolve leftover go.mod/go.sum conflict markers --- go.mod | 4 ---- go.sum | 17 ----------------- 2 files changed, 21 deletions(-) diff --git a/go.mod b/go.mod index c86ba9bd6..ac87ef35b 100644 --- a/go.mod +++ b/go.mod @@ -80,11 +80,7 @@ require ( golang.org/x/text v0.37.0 golang.org/x/tools v0.44.0 google.golang.org/api v0.280.0 -<<<<<<< HEAD google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 -======= - google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 ->>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 k8s.io/api v0.35.1 diff --git a/go.sum b/go.sum index 26c3952e1..43198cc52 100644 --- a/go.sum +++ b/go.sum @@ -1416,11 +1416,6 @@ 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= -<<<<<<< HEAD -======= -google.golang.org/api v0.277.0 h1:HJfyJUiNeBBUMai7ez8u14wkp/gH/I4wpGbbO9o+cSk= -google.golang.org/api v0.277.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= ->>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) 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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1433,15 +1428,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= -<<<<<<< HEAD google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -======= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= ->>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1450,11 +1438,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -<<<<<<< HEAD -======= -google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= -google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= ->>>>>>> 99537aa0 (chore: bump v13s to v0.0.0-20260528121134-739c7136ac8e (cve-priority rebase onto main)) google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= From 35b7b36d7a8b4ae9feaefed17a83df310587239f Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 2 Jun 2026 09:58:49 +0200 Subject: [PATCH 06/29] fix: address gosec int32 cast and update issue expectation --- integration_tests/issues_for_team.lua | 4 ++-- internal/vulnerability/models.go | 1 + internal/vulnerability/queries.go | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/integration_tests/issues_for_team.lua b/integration_tests/issues_for_team.lua index 6fe9ec3a9..8b6575e11 100644 --- a/integration_tests/issues_for_team.lua +++ b/integration_tests/issues_for_team.lua @@ -579,8 +579,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 ACT_NOW and 3 HIGH priority vulnerabilities", + severity = "CRITICAL", critical = 5, riskScore = 250, workload = { diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index 982781c97..ae61dc0f3 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -79,6 +79,7 @@ type ImageVulnerabilitySummary struct { Critical int `json:"critical"` Unassigned int `json:"unassigned"` LastUpdated *time.Time `json:"lastUpdated"` + StaleImageTag *string `json:"staleImageTag"` PriorityActNow int `json:"priorityActNow"` PriorityHigh int `json:"priorityHigh"` PriorityElevated int `json:"priorityElevated"` diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index 8ab7e1859..f72395647 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -701,7 +701,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) } @@ -723,7 +723,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) { From 4066d98af5781122c5c44548dd448fa25d793938 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:09:21 +0200 Subject: [PATCH 07/29] chore(deps): bump v13s api to af9d5e6 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ac87ef35b..3d2891d62 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-20260528121134-739c7136ac8e + github.com/nais/v13s/pkg/api v0.0.0-20260602103534-af9d5e6d27d3 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 diff --git a/go.sum b/go.sum index 43198cc52..8d2852525 100644 --- a/go.sum +++ b/go.sum @@ -815,6 +815,8 @@ github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe h1:CdRVopOihru4 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-20260528121134-739c7136ac8e h1:7fut4nxp6NlX7xS5SnwLkdyAhWanqmcpuClssn/cT8Q= github.com/nais/v13s/pkg/api v0.0.0-20260528121134-739c7136ac8e/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= +github.com/nais/v13s/pkg/api v0.0.0-20260602103534-af9d5e6d27d3 h1:ei4Q9fAPa0dtBo7aBwCUuVrDRXA9jtipqsU6PhOcPYw= +github.com/nais/v13s/pkg/api v0.0.0-20260602103534-af9d5e6d27d3/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= 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= From 11bee74fdedcaf5e3538c595355eb2ebcc132201 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:09:24 +0200 Subject: [PATCH 08/29] fix(vulnerability): adapt API to v13s risk-tier model --- integration_tests/issues_for_team.lua | 2 +- internal/graph/gengql/root_.generated.go | 15 +++-- .../graph/gengql/vulnerability.generated.go | 62 +++++-------------- internal/graph/schema/vulnerability.graphqls | 14 ++--- internal/graph/vulnerability.resolvers.go | 10 --- internal/issue/checker/workload_v13s.go | 34 +++++----- internal/vulnerability/fake/fakedata.go | 58 +++++++++-------- internal/vulnerability/models.go | 10 +-- internal/vulnerability/queries.go | 2 +- internal/vulnerability/sortfilter.go | 6 +- internal/vulnerability/transform.go | 33 ++++++---- 11 files changed, 106 insertions(+), 140 deletions(-) diff --git a/integration_tests/issues_for_team.lua b/integration_tests/issues_for_team.lua index 8b6575e11..105c6369c 100644 --- a/integration_tests/issues_for_team.lua +++ b/integration_tests/issues_for_team.lua @@ -579,7 +579,7 @@ Test.gql("VulnerableImageIssue", function(t) nodes = { { __typename = "VulnerableImageIssue", - message = "Image 'vulnerable-image' has 2 ACT_NOW and 3 HIGH priority vulnerabilities", + message = "Image 'vulnerable-image' has 2 IMMEDIATE and 3 HIGH risk-tier vulnerabilities", severity = "CRITICAL", critical = 5, riskScore = 250, diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index b89788be2..1a18acc7c 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -84,7 +84,6 @@ type ResolverRoot interface { ExternalIngressActNowVulnerabilityIssue() ExternalIngressActNowVulnerabilityIssueResolver ExternalIngressCriticalVulnerabilityIssue() ExternalIngressCriticalVulnerabilityIssueResolver FailedSynchronizationIssue() FailedSynchronizationIssueResolver - ImageVulnerabilitySummary() ImageVulnerabilitySummaryResolver Ingress() IngressResolver IngressMetrics() IngressMetricsResolver InstanceGroup() InstanceGroupResolver @@ -31630,16 +31629,16 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! - "Number of vulnerabilities with priority ACT_NOW." + "Number of vulnerabilities with risk tier IMMEDIATE." priorityActNow: Int! - "Number of vulnerabilities with priority HIGH." + "Number of vulnerabilities with risk tier HIGH." priorityHigh: Int! - "Number of vulnerabilities with priority ELEVATED." + "Number of vulnerabilities with risk tier ELEVATED." priorityElevated: Int! - "Number of vulnerabilities with priority MONITOR." + "Number of vulnerabilities with risk tier MONITOR." priorityMonitor: Int! "Timestamp of the last update of the vulnerability summary." @@ -31734,7 +31733,7 @@ type ImageVulnerability implements Node { enum CVEPriority { "Vulnerability is known to be actively exploited and requires immediate action." - ACT_NOW + IMMEDIATE "Vulnerability is associated with ransomware or has a high EPSS percentile." HIGH "Vulnerability has a critical or high severity and elevated EPSS percentile." @@ -31957,11 +31956,11 @@ enum VulnerabilitySummaryOrderByField { """ VULNERABILITY_SEVERITY_UNASSIGNED """ - Order by priority ACT_NOW count" + Order by IMMEDIATE risk-tier count" """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by priority HIGH count" + Order by HIGH risk-tier count" """ VULNERABILITY_PRIORITY_HIGH } diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index c80f86fbe..e5dd92780 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -32,9 +32,6 @@ type ContainerImageSBOMResolver interface { type ContainerImageWorkloadReferenceResolver interface { Workload(ctx context.Context, obj *vulnerability.ContainerImageWorkloadReference) (workload.Workload, error) } -type ImageVulnerabilitySummaryResolver interface { - StaleImageTag(ctx context.Context, obj *vulnerability.ImageVulnerabilitySummary) (*string, error) -} type TeamVulnerabilitySummaryResolver interface { RiskScoreTrend(ctx context.Context, obj *vulnerability.TeamVulnerabilitySummary) (vulnerability.TeamVulnerabilityRiskScoreTrend, error) } @@ -1562,7 +1559,7 @@ func (ec *executionContext) _ImageVulnerabilitySummary_staleImageTag(ctx context return ec.fieldContext_ImageVulnerabilitySummary_staleImageTag(ctx, field) }, func(ctx context.Context) (any, error) { - return ec.Resolvers.ImageVulnerabilitySummary().StaleImageTag(ctx, obj) + return obj.StaleImageTag, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { @@ -1573,7 +1570,7 @@ func (ec *executionContext) _ImageVulnerabilitySummary_staleImageTag(ctx context ) } func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_staleImageTag(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, true, true, errors.New("field of type String does not have child fields")) + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type String does not have child fields")) } func (ec *executionContext) _ImageVulnerabilitySuppression_state(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySuppression) (ret graphql.Marshaler) { @@ -4155,93 +4152,62 @@ func (ec *executionContext) _ImageVulnerabilitySummary(ctx context.Context, sel case "total": out.Values[i] = ec._ImageVulnerabilitySummary_total(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "riskScore": out.Values[i] = ec._ImageVulnerabilitySummary_riskScore(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "low": out.Values[i] = ec._ImageVulnerabilitySummary_low(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "medium": out.Values[i] = ec._ImageVulnerabilitySummary_medium(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "high": out.Values[i] = ec._ImageVulnerabilitySummary_high(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "critical": out.Values[i] = ec._ImageVulnerabilitySummary_critical(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "unassigned": out.Values[i] = ec._ImageVulnerabilitySummary_unassigned(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "priorityActNow": out.Values[i] = ec._ImageVulnerabilitySummary_priorityActNow(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "priorityHigh": out.Values[i] = ec._ImageVulnerabilitySummary_priorityHigh(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "priorityElevated": out.Values[i] = ec._ImageVulnerabilitySummary_priorityElevated(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "priorityMonitor": out.Values[i] = ec._ImageVulnerabilitySummary_priorityMonitor(ctx, field, obj) if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + out.Invalids++ } case "lastUpdated": out.Values[i] = ec._ImageVulnerabilitySummary_lastUpdated(ctx, field, obj) case "staleImageTag": - field := field - - innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._ImageVulnerabilitySummary_staleImageTag(ctx, field, obj) - 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) }) + out.Values[i] = ec._ImageVulnerabilitySummary_staleImageTag(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index fd8ef3a19..cb2441a05 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -291,16 +291,16 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! - "Number of vulnerabilities with priority ACT_NOW." + "Number of vulnerabilities with risk tier IMMEDIATE." priorityActNow: Int! - "Number of vulnerabilities with priority HIGH." + "Number of vulnerabilities with risk tier HIGH." priorityHigh: Int! - "Number of vulnerabilities with priority ELEVATED." + "Number of vulnerabilities with risk tier ELEVATED." priorityElevated: Int! - "Number of vulnerabilities with priority MONITOR." + "Number of vulnerabilities with risk tier MONITOR." priorityMonitor: Int! "Timestamp of the last update of the vulnerability summary." @@ -395,7 +395,7 @@ type ImageVulnerability implements Node { enum CVEPriority { "Vulnerability is known to be actively exploited and requires immediate action." - ACT_NOW + IMMEDIATE "Vulnerability is associated with ransomware or has a high EPSS percentile." HIGH "Vulnerability has a critical or high severity and elevated EPSS percentile." @@ -618,11 +618,11 @@ enum VulnerabilitySummaryOrderByField { """ VULNERABILITY_SEVERITY_UNASSIGNED """ - Order by priority ACT_NOW count" + Order by IMMEDIATE risk-tier count" """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by priority HIGH count" + Order by HIGH risk-tier count" """ VULNERABILITY_PRIORITY_HIGH } diff --git a/internal/graph/vulnerability.resolvers.go b/internal/graph/vulnerability.resolvers.go index 7529d8d23..fbc3c706e 100644 --- a/internal/graph/vulnerability.resolvers.go +++ b/internal/graph/vulnerability.resolvers.go @@ -2,7 +2,6 @@ package graph import ( "context" - "fmt" "time" "github.com/nais/api/internal/auth/authz" @@ -81,10 +80,6 @@ func (r *containerImageWorkloadReferenceResolver) Workload(ctx context.Context, return getWorkload(ctx, obj.Reference, obj.TeamSlug, environmentmapper.EnvironmentName(obj.EnvironmentName)) } -func (r *imageVulnerabilitySummaryResolver) StaleImageTag(ctx context.Context, obj *vulnerability.ImageVulnerabilitySummary) (*string, error) { - panic(fmt.Errorf("not implemented: StaleImageTag - staleImageTag")) -} - func (r *jobResolver) ImageVulnerabilityHistory(ctx context.Context, obj *job.Job, from scalar.Date) (*vulnerability.ImageVulnerabilityHistory, error) { return vulnerability.GetWorkloadVulnerabilityHistoryForWorkload(ctx, obj, from.Time()) } @@ -178,10 +173,6 @@ func (r *Resolver) ContainerImageWorkloadReference() gengql.ContainerImageWorklo return &containerImageWorkloadReferenceResolver{r} } -func (r *Resolver) ImageVulnerabilitySummary() gengql.ImageVulnerabilitySummaryResolver { - return &imageVulnerabilitySummaryResolver{r} -} - func (r *Resolver) TeamVulnerabilitySummary() gengql.TeamVulnerabilitySummaryResolver { return &teamVulnerabilitySummaryResolver{r} } @@ -194,7 +185,6 @@ type ( cVEResolver struct{ *Resolver } containerImageSBOMResolver struct{ *Resolver } containerImageWorkloadReferenceResolver struct{ *Resolver } - imageVulnerabilitySummaryResolver struct{ *Resolver } teamVulnerabilitySummaryResolver struct{ *Resolver } workloadVulnerabilitySummaryResolver struct{ *Resolver } ) diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 827a8d994..5fc5f30ff 100644 --- a/internal/issue/checker/workload_v13s.go +++ b/internal/issue/checker/workload_v13s.go @@ -38,10 +38,10 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... ImageTag: "tag1", }, VulnerabilitySummary: &vulnerabilities.Summary{ - Critical: 5, - RiskScore: 250, - PriorityActNow: 2, - PriorityHigh: 3, + Critical: 5, + RiskScore: 250, + ActNow: 2, + HighRisk: 3, }, SbomStatus: &vulnerabilities.SbomStatusInfo{ Status: vulnerabilities.SbomStatus_SBOM_STATUS_READY, @@ -72,10 +72,10 @@ func (f fakeV13sClient) ListVulnerabilitySummaries(ctx context.Context, opts ... ImageTag: "tag1", }, VulnerabilitySummary: &vulnerabilities.Summary{ - Critical: 5, - RiskScore: 250, - PriorityActNow: 2, - PriorityHigh: 3, + Critical: 5, + RiskScore: 250, + ActNow: 2, + HighRisk: 3, }, SbomStatus: &vulnerabilities.SbomStatusInfo{ Status: vulnerabilities.SbomStatus_SBOM_STATUS_READY, @@ -186,9 +186,9 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { } summary := node.VulnerabilitySummary - if summary != nil && (summary.PriorityActNow > 0 || summary.PriorityHigh > 0) { + if summary != nil && (summary.ActNow > 0 || summary.HighRisk > 0) { severity := issue.SeverityWarning - if summary.PriorityActNow > 0 { + if summary.ActNow > 0 { severity = issue.SeverityCritical } ret = append(ret, &Issue{ @@ -199,10 +199,10 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { Env: environmentmapper.EnvironmentName(node.Workload.GetCluster()), Severity: severity, Message: fmt.Sprintf( - "Image '%s' has %d ACT_NOW and %d HIGH priority vulnerabilities", + "Image '%s' has %d IMMEDIATE and %d HIGH risk-tier vulnerabilities", node.Workload.ImageName, - summary.PriorityActNow, - summary.PriorityHigh, + summary.ActNow, + summary.HighRisk, ), IssueDetails: issue.VulnerableImageIssueDetails{ Critical: int(summary.Critical), @@ -296,7 +296,7 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { continue } - if node.VulnerabilitySummary == nil || node.VulnerabilitySummary.PriorityActNow == 0 { + if node.VulnerabilitySummary == nil || node.VulnerabilitySummary.ActNow == 0 { continue } @@ -320,12 +320,12 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { Env: env, Severity: issue.SeverityCritical, Message: fmt.Sprintf( - "Workload with external ingresses %s has %d ACT_NOW priority vulnerabilities", + "Workload with external ingresses %s has %d IMMEDIATE risk-tier vulnerabilities", strings.Join(externalIngresses, ", "), - node.VulnerabilitySummary.PriorityActNow, + node.VulnerabilitySummary.ActNow, ), IssueDetails: issue.ExternalIngressActNowVulnerabilityIssueDetails{ - PriorityActNow: int(node.VulnerabilitySummary.PriorityActNow), + PriorityActNow: int(node.VulnerabilitySummary.ActNow), Ingresses: externalIngresses, }, }) diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index e609af606..85b333635 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -79,19 +79,19 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact imageName := parts[0] imageTag := parts[1] 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()), - PriorityActNow: vulnFactor, - PriorityHigh: vulnFactor * 2, - PriorityElevated: vulnFactor * 3, - PriorityMonitor: vulnFactor * 4, + 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, } if name == "no-errors" { @@ -124,39 +124,44 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact func createVulnerabilities(w *vulnerabilities.WorkloadSummary) []*vulnerabilities.Vulnerability { findings := make([]*vulnerabilities.Vulnerability, 0) - priorities := []vulnerabilities.Priority{ - vulnerabilities.Priority_PRIORITY_ACT_NOW, - vulnerabilities.Priority_PRIORITY_HIGH, - vulnerabilities.Priority_PRIORITY_ELEVATED, - vulnerabilities.Priority_PRIORITY_MONITOR, + tiers := []vulnerabilities.RiskTier{ + vulnerabilities.RiskTier_ACT_NOW, + vulnerabilities.RiskTier_HIGH_RISK, + vulnerabilities.RiskTier_ELEVATED_RISK, + vulnerabilities.RiskTier_MONITOR, } idx := 0 - nextPriority := func() vulnerabilities.Priority { - p := priorities[idx%len(priorities)] + nextRiskTier := func() vulnerabilities.RiskTier { + p := tiers[idx%len(tiers)] idx++ return p } epssScore := 0.85 epssPercentile := 97.3 for i := range w.VulnerabilitySummary.Critical { - findings = append(findings, createVulnerability(vulnerabilities.Severity_CRITICAL, fmt.Sprintf("some-component-%d", i), nextPriority(), &epssScore, &epssPercentile, true, false)) + _ = nextRiskTier() + 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), nextPriority(), nil, nil, false, false)) + _ = nextRiskTier() + 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), nextPriority(), nil, nil, false, false)) + _ = nextRiskTier() + 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), nextPriority(), nil, nil, false, false)) + _ = nextRiskTier() + 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), nextPriority(), nil, nil, false, false)) + _ = nextRiskTier() + 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, priority vulnerabilities.Priority, epssScore, epssPercentile *float64, hasKevEntry, knownRansomwareUse bool) *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), @@ -167,7 +172,6 @@ func createVulnerability(severity vulnerabilities.Severity, componentName string Link: "", Severity: severity, References: nil, - Priority: priority, EpssScore: epssScore, EpssPercentile: epssPercentile, HasKevEntry: hasKevEntry, diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index ae61dc0f3..4286aeb22 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -424,15 +424,15 @@ type VulnerabilityFixSample struct { type CVEPriority string const ( - CVEPriorityActNow CVEPriority = "ACT_NOW" - CVEPriorityHigh CVEPriority = "HIGH" - CVEPriorityElevated CVEPriority = "ELEVATED" - CVEPriorityMonitor CVEPriority = "MONITOR" + CVEPriorityImmediate CVEPriority = "IMMEDIATE" + CVEPriorityHigh CVEPriority = "HIGH" + CVEPriorityElevated CVEPriority = "ELEVATED" + CVEPriorityMonitor CVEPriority = "MONITOR" ) func (e CVEPriority) IsValid() bool { switch e { - case CVEPriorityActNow, CVEPriorityHigh, CVEPriorityElevated, CVEPriorityMonitor: + case CVEPriorityImmediate, CVEPriorityHigh, CVEPriorityElevated, CVEPriorityMonitor: return true } return false diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index f72395647..ecc676248 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -656,7 +656,7 @@ func ListCVEs(ctx context.Context, page *pagination.Pagination, orderBy *CVEOrde case CVEOrderFieldAffectedWorkloadsCount: field = vulnerabilities.OrderByAffectedWorkloads case CVEOrderFieldPriority: - field = vulnerabilities.OrderByPriority + field = vulnerabilities.OrderByTopRiskTier default: field = vulnerabilities.OrderByCvssScore } diff --git a/internal/vulnerability/sortfilter.go b/internal/vulnerability/sortfilter.go index 49625cb64..2ff475293 100644 --- a/internal/vulnerability/sortfilter.go +++ b/internal/vulnerability/sortfilter.go @@ -16,7 +16,7 @@ var SortFilterImageVulnerabilities = map[ImageVulnerabilityOrderField]vulnerabil "STATE": vulnerabilities.OrderByReason, "SUPPRESSED": vulnerabilities.OrderBySuppressed, "SEVERITY_SINCE": vulnerabilities.OrderBySeveritySince, - "PRIORITY": vulnerabilities.OrderByPriority, + "PRIORITY": vulnerabilities.OrderByTopRiskTier, } var SortFilterWorkloadSummaries = map[VulnerabilitySummaryOrderByField]vulnerabilities.OrderByField{ @@ -28,8 +28,8 @@ var SortFilterWorkloadSummaries = map[VulnerabilitySummaryOrderByField]vulnerabi "VULNERABILITY_SEVERITY_MEDIUM": vulnerabilities.OrderByMedium, "VULNERABILITY_SEVERITY_LOW": vulnerabilities.OrderByLow, "VULNERABILITY_SEVERITY_UNASSIGNED": vulnerabilities.OrderByUnassigned, - "VULNERABILITY_PRIORITY_ACT_NOW": vulnerabilities.OrderByPriorityActNow, - "VULNERABILITY_PRIORITY_HIGH": vulnerabilities.OrderByPriorityHigh, + "VULNERABILITY_PRIORITY_ACT_NOW": vulnerabilities.OrderByActNow, + "VULNERABILITY_PRIORITY_HIGH": vulnerabilities.OrderByHighRisk, } const ( diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 6b60cb8e6..b98f59118 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -81,10 +81,10 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa Total: int(v13sSummary.Total), RiskScore: int(v13sSummary.RiskScore), LastUpdated: lastUpdated, - PriorityActNow: int(v13sSummary.PriorityActNow), - PriorityHigh: int(v13sSummary.PriorityHigh), - PriorityElevated: int(v13sSummary.PriorityElevated), - PriorityMonitor: int(v13sSummary.PriorityMonitor), + PriorityActNow: int(v13sSummary.ActNow), + PriorityHigh: int(v13sSummary.HighRisk), + PriorityElevated: int(v13sSummary.ElevatedRisk), + PriorityMonitor: int(v13sSummary.Monitor), } return &WorkloadVulnerabilitySummary{ @@ -107,7 +107,7 @@ func toCVE(cve *vulnerabilities.Cve) *CVE { DetailsLink: cve.Link, CVSSScore: cve.CvssScore, Severity: ImageVulnerabilitySeverity(cve.Severity.String()), - Priority: parseCVEPriority(cve.Priority), + Priority: parseCVEPriority(cve), EpssScore: cve.EpssScore, EpssPercentile: cve.EpssPercentile, HasKevEntry: cve.HasKevEntry, @@ -115,17 +115,24 @@ func toCVE(cve *vulnerabilities.Cve) *CVE { } } -func parseCVEPriority(p vulnerabilities.Priority) CVEPriority { - switch p { - case vulnerabilities.Priority_PRIORITY_ACT_NOW: - return CVEPriorityActNow - case vulnerabilities.Priority_PRIORITY_HIGH: +func parseCVEPriority(cve *vulnerabilities.Cve) CVEPriority { + if cve == nil { + return CVEPriorityMonitor + } + + if cve.GetHasKevEntry() { + return CVEPriorityImmediate + } + + if cve.GetKnownRansomwareUse() || cve.GetEpssPercentile() >= 0.90 { return CVEPriorityHigh - case vulnerabilities.Priority_PRIORITY_ELEVATED: + } + + if (cve.GetSeverity() == vulnerabilities.Severity_CRITICAL || cve.GetSeverity() == vulnerabilities.Severity_HIGH) && cve.GetEpssPercentile() >= 0.50 { return CVEPriorityElevated - default: - return CVEPriorityMonitor } + + return CVEPriorityMonitor } func mapVulnerabilitySeverity(severity ImageVulnerabilitySeverity) vulnerabilities.Severity { From 2a8872557ec6e851ba1a5a5d4a6d083d3046dc70 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:23:31 +0200 Subject: [PATCH 09/29] test(vulnerability): cover CVE risk-tier derivation --- internal/graph/gengql/root_.generated.go | 2 +- internal/graph/schema/issues.graphqls | 2 +- internal/vulnerability/transform_test.go | 86 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 internal/vulnerability/transform_test.go diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 1a18acc7c..2738661c8 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -23387,7 +23387,7 @@ type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { ingresses: [String!]! } -"Raised when a workload with external ingresses has one or more ACT_NOW priority vulnerabilities." +"Raised when a workload with external ingresses has one or more IMMEDIATE risk-tier vulnerabilities." type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! diff --git a/internal/graph/schema/issues.graphqls b/internal/graph/schema/issues.graphqls index 2932e6e7f..11b41ee1f 100644 --- a/internal/graph/schema/issues.graphqls +++ b/internal/graph/schema/issues.graphqls @@ -192,7 +192,7 @@ type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { ingresses: [String!]! } -"Raised when a workload with external ingresses has one or more ACT_NOW priority vulnerabilities." +"Raised when a workload with external ingresses has one or more IMMEDIATE risk-tier vulnerabilities." type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! diff --git a/internal/vulnerability/transform_test.go b/internal/vulnerability/transform_test.go new file mode 100644 index 000000000..3ecc35018 --- /dev/null +++ b/internal/vulnerability/transform_test.go @@ -0,0 +1,86 @@ +package vulnerability + +import ( + "testing" + + "github.com/nais/v13s/pkg/api/vulnerabilities" +) + +func TestParseCVEPriority(t *testing.T) { + tests := []struct { + name string + cve *vulnerabilities.Cve + want CVEPriority + }{ + { + name: "nil cve defaults to monitor", + cve: nil, + want: CVEPriorityMonitor, + }, + { + name: "kev is immediate", + cve: &vulnerabilities.Cve{ + HasKevEntry: true, + KnownRansomwareUse: true, + EpssPercentile: new(0.99), + Severity: vulnerabilities.Severity_CRITICAL, + }, + want: CVEPriorityImmediate, + }, + { + name: "ransomware use is high", + cve: &vulnerabilities.Cve{ + KnownRansomwareUse: true, + }, + want: CVEPriorityHigh, + }, + { + name: "epss percentile threshold is high", + cve: &vulnerabilities.Cve{ + EpssPercentile: new(0.90), + }, + want: CVEPriorityHigh, + }, + { + name: "critical severity and elevated epss is elevated", + cve: &vulnerabilities.Cve{ + Severity: vulnerabilities.Severity_CRITICAL, + EpssPercentile: new(0.50), + }, + want: CVEPriorityElevated, + }, + { + name: "high severity and elevated epss is elevated", + cve: &vulnerabilities.Cve{ + Severity: vulnerabilities.Severity_HIGH, + EpssPercentile: new(0.50), + }, + want: CVEPriorityElevated, + }, + { + name: "low severity and elevated epss stays monitor", + cve: &vulnerabilities.Cve{ + Severity: vulnerabilities.Severity_LOW, + EpssPercentile: new(0.70), + }, + want: CVEPriorityMonitor, + }, + { + name: "high severity below elevated epss stays monitor", + cve: &vulnerabilities.Cve{ + Severity: vulnerabilities.Severity_HIGH, + EpssPercentile: new(0.49), + }, + want: CVEPriorityMonitor, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parseCVEPriority(tt.cve) + if got != tt.want { + t.Fatalf("parseCVEPriority() = %s, want %s", got, tt.want) + } + }) + } +} From 454089c2a6ab4001838d4362df870c8cc3f2f78e Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:18:27 +0200 Subject: [PATCH 10/29] feat(vulnerability): expose fixVersion on image vulnerabilities --- internal/graph/gengql/root_.generated.go | 13 ++++++++++ .../graph/gengql/vulnerability.generated.go | 25 +++++++++++++++++++ internal/graph/schema/vulnerability.graphqls | 3 +++ internal/vulnerability/models.go | 1 + internal/vulnerability/transform.go | 1 + 5 files changed, 43 insertions(+) diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 2738661c8..c3cb92f65 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1050,6 +1050,7 @@ type ComplexityRoot struct { ImageVulnerability struct { CvssScore func(childComplexity int) int Description func(childComplexity int) int + FixVersion func(childComplexity int) int ID func(childComplexity int) int Identifier func(childComplexity int) int Package func(childComplexity int) int @@ -7237,6 +7238,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerability.Description(childComplexity), true + case "ImageVulnerability.fixVersion": + if e.ComplexityRoot.ImageVulnerability.FixVersion == nil { + break + } + + return e.ComplexityRoot.ImageVulnerability.FixVersion(childComplexity), true + case "ImageVulnerability.id": if e.ComplexityRoot.ImageVulnerability.ID == nil { break @@ -31719,6 +31727,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." @@ -34008,6 +34019,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": diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index e5dd92780..e892fa1af 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -935,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, @@ -3928,6 +3951,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": diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index cb2441a05..2497e6bd1 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -381,6 +381,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." diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index 4286aeb22..800acb60c 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -48,6 +48,7 @@ type ImageVulnerability struct { CvssScore *float64 `json:"cvssScore"` 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"` diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index b98f59118..e2eaade37 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -28,6 +28,7 @@ func toImageVulnerability(v *vulnerabilities.Vulnerability) *ImageVulnerability CvssScore: v.GetCve().CvssScore, Description: description, Package: v.Package, + FixVersion: v.FixVersion, SeveritySince: severitySince, VulnerabilityDetailsLink: v.Cve.Link, } From 5878acd1e728452cec0eec6ac2b9186cdb029fa1 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:43:27 +0200 Subject: [PATCH 11/29] fix(vulnerability): handle RISK_TIER_UNSPECIFIED in risk-tier mapping --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3d2891d62..a0fbce8b8 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-20260602103534-af9d5e6d27d3 + github.com/nais/v13s/pkg/api v0.0.0-20260603094115-97cc35c043d9 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 diff --git a/go.sum b/go.sum index 8d2852525..44ce5d8e6 100644 --- a/go.sum +++ b/go.sum @@ -817,6 +817,8 @@ github.com/nais/v13s/pkg/api v0.0.0-20260528121134-739c7136ac8e h1:7fut4nxp6NlX7 github.com/nais/v13s/pkg/api v0.0.0-20260528121134-739c7136ac8e/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= github.com/nais/v13s/pkg/api v0.0.0-20260602103534-af9d5e6d27d3 h1:ei4Q9fAPa0dtBo7aBwCUuVrDRXA9jtipqsU6PhOcPYw= github.com/nais/v13s/pkg/api v0.0.0-20260602103534-af9d5e6d27d3/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= +github.com/nais/v13s/pkg/api v0.0.0-20260603094115-97cc35c043d9 h1:lwdg5KcNrjpBhqhFQtD1YK/GQmVBve1inZanhEDgGco= +github.com/nais/v13s/pkg/api v0.0.0-20260603094115-97cc35c043d9/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= 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= From 2132f96abbd2117924abed1b8a892695d5ff926a Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:52:25 +0200 Subject: [PATCH 12/29] feat(vulnerability): expose KEV and EPSS fields on ImageVulnerability --- internal/graph/gengql/root_.generated.go | 52 +++++++++ .../graph/gengql/vulnerability.generated.go | 106 ++++++++++++++++++ internal/graph/schema/vulnerability.graphqls | 12 ++ internal/vulnerability/models.go | 4 + internal/vulnerability/transform.go | 4 + 5 files changed, 178 insertions(+) diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index c3cb92f65..c7fb0f78a 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1050,9 +1050,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 @@ -7238,6 +7242,20 @@ 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 @@ -7245,6 +7263,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin 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 @@ -7259,6 +7284,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 @@ -31740,6 +31772,18 @@ 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 { @@ -34029,6 +34073,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) } diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index e892fa1af..5f0840d47 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -1059,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, @@ -3964,6 +4056,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)) } diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index 2497e6bd1..38ab9b061 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -394,6 +394,18 @@ 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 { diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index 800acb60c..e82214391 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -46,6 +46,10 @@ 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"` diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index e2eaade37..6aad4e9f3 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -26,6 +26,10 @@ 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, From e9d2a7c8c286057c53f071731c7fdf2e34ba2319 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:12:06 +0200 Subject: [PATCH 13/29] fix(vulnerability): map PRIORITY sort to cve priority order --- internal/vulnerability/queries.go | 2 +- internal/vulnerability/sortfilter.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index ecc676248..f72395647 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -656,7 +656,7 @@ func ListCVEs(ctx context.Context, page *pagination.Pagination, orderBy *CVEOrde case CVEOrderFieldAffectedWorkloadsCount: field = vulnerabilities.OrderByAffectedWorkloads case CVEOrderFieldPriority: - field = vulnerabilities.OrderByTopRiskTier + field = vulnerabilities.OrderByPriority default: field = vulnerabilities.OrderByCvssScore } diff --git a/internal/vulnerability/sortfilter.go b/internal/vulnerability/sortfilter.go index 2ff475293..a682a60c7 100644 --- a/internal/vulnerability/sortfilter.go +++ b/internal/vulnerability/sortfilter.go @@ -16,7 +16,7 @@ var SortFilterImageVulnerabilities = map[ImageVulnerabilityOrderField]vulnerabil "STATE": vulnerabilities.OrderByReason, "SUPPRESSED": vulnerabilities.OrderBySuppressed, "SEVERITY_SINCE": vulnerabilities.OrderBySeveritySince, - "PRIORITY": vulnerabilities.OrderByTopRiskTier, + "PRIORITY": vulnerabilities.OrderByPriority, } var SortFilterWorkloadSummaries = map[VulnerabilitySummaryOrderByField]vulnerabilities.OrderByField{ From ed07236e6efa2079f425d97b60e48bd80b711b2d Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:09:54 +0200 Subject: [PATCH 14/29] chore(deps): bump v13s api after exploitable removal --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index a0fbce8b8..d421fe07f 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-20260603094115-97cc35c043d9 + github.com/nais/v13s/pkg/api v0.0.0-20260604080807-5ff2f400c716 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 diff --git a/go.sum b/go.sum index 44ce5d8e6..b8a7356bf 100644 --- a/go.sum +++ b/go.sum @@ -813,12 +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-20260528121134-739c7136ac8e h1:7fut4nxp6NlX7xS5SnwLkdyAhWanqmcpuClssn/cT8Q= -github.com/nais/v13s/pkg/api v0.0.0-20260528121134-739c7136ac8e/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= -github.com/nais/v13s/pkg/api v0.0.0-20260602103534-af9d5e6d27d3 h1:ei4Q9fAPa0dtBo7aBwCUuVrDRXA9jtipqsU6PhOcPYw= -github.com/nais/v13s/pkg/api v0.0.0-20260602103534-af9d5e6d27d3/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= -github.com/nais/v13s/pkg/api v0.0.0-20260603094115-97cc35c043d9 h1:lwdg5KcNrjpBhqhFQtD1YK/GQmVBve1inZanhEDgGco= -github.com/nais/v13s/pkg/api v0.0.0-20260603094115-97cc35c043d9/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= +github.com/nais/v13s/pkg/api v0.0.0-20260604080807-5ff2f400c716 h1:FpEOQH7TP50xuVCkkcMk+ZaSRnxhmHZmuhDVPGL56sU= +github.com/nais/v13s/pkg/api v0.0.0-20260604080807-5ff2f400c716/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= 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= From 424abae28d4a60fb4ac8d7ffe03ae5f3f56a828c Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:34:57 +0200 Subject: [PATCH 15/29] fix(vulnerability): clean enum docs and normalize fake EPSS percentile --- internal/graph/gengql/root_.generated.go | 16 ++++++++-------- internal/graph/schema/vulnerability.graphqls | 16 ++++++++-------- internal/vulnerability/fake/fakedata.go | 19 +------------------ 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index c7fb0f78a..224d8098f 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -31987,35 +31987,35 @@ 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 IMMEDIATE risk-tier count" + Order by IMMEDIATE risk-tier count. """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by HIGH risk-tier count" + Order by HIGH risk-tier count. """ VULNERABILITY_PRIORITY_HIGH } diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index 38ab9b061..9f98b965b 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -609,35 +609,35 @@ 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 IMMEDIATE risk-tier count" + Order by IMMEDIATE risk-tier count. """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by HIGH risk-tier count" + Order by HIGH risk-tier count. """ VULNERABILITY_PRIORITY_HIGH } diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index 85b333635..10eb6329b 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -124,38 +124,21 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact func createVulnerabilities(w *vulnerabilities.WorkloadSummary) []*vulnerabilities.Vulnerability { findings := make([]*vulnerabilities.Vulnerability, 0) - tiers := []vulnerabilities.RiskTier{ - vulnerabilities.RiskTier_ACT_NOW, - vulnerabilities.RiskTier_HIGH_RISK, - vulnerabilities.RiskTier_ELEVATED_RISK, - vulnerabilities.RiskTier_MONITOR, - } - idx := 0 - nextRiskTier := func() vulnerabilities.RiskTier { - p := tiers[idx%len(tiers)] - idx++ - return p - } epssScore := 0.85 - epssPercentile := 97.3 + epssPercentile := 0.973 for i := range w.VulnerabilitySummary.Critical { - _ = nextRiskTier() findings = append(findings, createVulnerability(vulnerabilities.Severity_CRITICAL, fmt.Sprintf("some-component-%d", i), &epssScore, &epssPercentile, true, false)) } for i := range w.VulnerabilitySummary.High { - _ = nextRiskTier() findings = append(findings, createVulnerability(vulnerabilities.Severity_HIGH, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } for i := range w.VulnerabilitySummary.Medium { - _ = nextRiskTier() findings = append(findings, createVulnerability(vulnerabilities.Severity_MEDIUM, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } for i := range w.VulnerabilitySummary.Low { - _ = nextRiskTier() findings = append(findings, createVulnerability(vulnerabilities.Severity_LOW, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } for i := range w.VulnerabilitySummary.Unassigned { - _ = nextRiskTier() findings = append(findings, createVulnerability(vulnerabilities.Severity_UNASSIGNED, fmt.Sprintf("some-component-%d", i), nil, nil, false, false)) } return findings From e7866c4e33b943203e2c4699398ea9c4324badeb Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:42:55 +0200 Subject: [PATCH 16/29] feat(vulnerability): add ransomwareCount, highEpssCount, and topRiskTier to ImageVulnerabilitySummary --- go.mod | 10 +- go.sum | 8 ++ internal/graph/gengql/root_.generated.go | 39 ++++++++ .../graph/gengql/vulnerability.generated.go | 97 +++++++++++++++++++ internal/graph/schema/vulnerability.graphqls | 9 ++ internal/vulnerability/fake/fakedata.go | 29 +++--- internal/vulnerability/fake/v13s.go | 22 +++-- internal/vulnerability/models.go | 29 +++--- internal/vulnerability/queries.go | 46 ++++++--- internal/vulnerability/transform.go | 22 +++++ 10 files changed, 257 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index d421fe07f..b9d1d8f8d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/nais/api -go 1.26.3 +go 1.26.4 tool ( github.com/99designs/gqlgen @@ -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-20260604080807-5ff2f400c716 + github.com/nais/v13s/pkg/api v0.0.0-20260609085403-62cd3b4768b7 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 @@ -79,7 +79,7 @@ require ( golang.org/x/sync v0.20.0 golang.org/x/text v0.37.0 golang.org/x/tools v0.44.0 - google.golang.org/api v0.280.0 + google.golang.org/api v0.283.0 google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 @@ -240,7 +240,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 @@ -465,7 +465,7 @@ require ( gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genai v1.54.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index b8a7356bf..a13fe6b14 100644 --- a/go.sum +++ b/go.sum @@ -526,6 +526,8 @@ 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= @@ -815,6 +817,8 @@ github.com/nais/unleasherator v0.0.0-20251216221129-efebc54203fe h1:CdRVopOihru4 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-20260604080807-5ff2f400c716 h1:FpEOQH7TP50xuVCkkcMk+ZaSRnxhmHZmuhDVPGL56sU= github.com/nais/v13s/pkg/api v0.0.0-20260604080807-5ff2f400c716/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= +github.com/nais/v13s/pkg/api v0.0.0-20260609085403-62cd3b4768b7 h1:mKyZ86EL7tvEaGuY6gj0IDmdYf1cKc2ujG0jer1RVpA= +github.com/nais/v13s/pkg/api v0.0.0-20260609085403-62cd3b4768b7/go.mod h1:D1ungKHGg4t242rWJXdusLiOs6clseEezpt1o1pU4aE= 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= @@ -1418,6 +1422,8 @@ 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.283.0 h1:0lkp8u0MPwJVHqRL+nJlMAoZVVzbmiXmFHXMOTmSPik= +google.golang.org/api v0.283.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= 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= @@ -1432,6 +1438,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 224d8098f..2e68ebeb1 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1087,6 +1087,7 @@ type ComplexityRoot struct { ImageVulnerabilitySummary struct { Critical func(childComplexity int) int High func(childComplexity int) int + HighEpssCount func(childComplexity int) int LastUpdated func(childComplexity int) int Low func(childComplexity int) int Medium func(childComplexity int) int @@ -1094,8 +1095,10 @@ type ComplexityRoot struct { PriorityElevated func(childComplexity int) int PriorityHigh func(childComplexity int) int PriorityMonitor func(childComplexity int) int + RansomwareCount func(childComplexity int) int RiskScore func(childComplexity int) int StaleImageTag func(childComplexity int) int + TopRiskTier func(childComplexity int) int Total func(childComplexity int) int Unassigned func(childComplexity int) int } @@ -7396,6 +7399,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.High(childComplexity), true + case "ImageVulnerabilitySummary.highEpssCount": + if e.ComplexityRoot.ImageVulnerabilitySummary.HighEpssCount == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.HighEpssCount(childComplexity), true + case "ImageVulnerabilitySummary.lastUpdated": if e.ComplexityRoot.ImageVulnerabilitySummary.LastUpdated == nil { break @@ -7445,6 +7455,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.PriorityMonitor(childComplexity), true + case "ImageVulnerabilitySummary.ransomwareCount": + if e.ComplexityRoot.ImageVulnerabilitySummary.RansomwareCount == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.RansomwareCount(childComplexity), true + case "ImageVulnerabilitySummary.riskScore": if e.ComplexityRoot.ImageVulnerabilitySummary.RiskScore == nil { break @@ -7459,6 +7476,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.StaleImageTag(childComplexity), true + case "ImageVulnerabilitySummary.topRiskTier": + if e.ComplexityRoot.ImageVulnerabilitySummary.TopRiskTier == nil { + break + } + + return e.ComplexityRoot.ImageVulnerabilitySummary.TopRiskTier(childComplexity), true + case "ImageVulnerabilitySummary.total": if e.ComplexityRoot.ImageVulnerabilitySummary.Total == nil { break @@ -31681,6 +31705,15 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with risk tier MONITOR." priorityMonitor: Int! + "Number of vulnerabilities associated with known ransomware campaigns." + ransomwareCount: Int! + + "Number of vulnerabilities with a high EPSS percentile (≥ 0.90)." + highEpssCount: Int! + + "The highest risk tier among vulnerabilities in this summary." + topRiskTier: CVEPriority + "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -34149,6 +34182,12 @@ func (ec *executionContext) childFields_ImageVulnerabilitySummary(ctx context.Co return ec.fieldContext_ImageVulnerabilitySummary_priorityElevated(ctx, field) case "priorityMonitor": return ec.fieldContext_ImageVulnerabilitySummary_priorityMonitor(ctx, field) + case "ransomwareCount": + return ec.fieldContext_ImageVulnerabilitySummary_ransomwareCount(ctx, field) + case "highEpssCount": + return ec.fieldContext_ImageVulnerabilitySummary_highEpssCount(ctx, field) + case "topRiskTier": + return ec.fieldContext_ImageVulnerabilitySummary_topRiskTier(ctx, field) case "lastUpdated": return ec.fieldContext_ImageVulnerabilitySummary_lastUpdated(ctx, field) case "staleImageTag": diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index 5f0840d47..3431f820d 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -1642,6 +1642,75 @@ func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_priorityMonit return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) } +func (ec *executionContext) _ImageVulnerabilitySummary_ransomwareCount(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_ransomwareCount(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.RansomwareCount, 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_ransomwareCount(_ 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_highEpssCount(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_highEpssCount(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.HighEpssCount, 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_highEpssCount(_ 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_topRiskTier(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_topRiskTier(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.TopRiskTier, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *vulnerability.CVEPriority) graphql.Marshaler { + return ec.marshalOCVEPriority2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_topRiskTier(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type CVEPriority does not have child fields")) +} + func (ec *executionContext) _ImageVulnerabilitySummary_lastUpdated(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -4335,6 +4404,18 @@ func (ec *executionContext) _ImageVulnerabilitySummary(ctx context.Context, sel if out.Values[i] == graphql.Null { out.Invalids++ } + case "ransomwareCount": + out.Values[i] = ec._ImageVulnerabilitySummary_ransomwareCount(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "highEpssCount": + out.Values[i] = ec._ImageVulnerabilitySummary_highEpssCount(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "topRiskTier": + out.Values[i] = ec._ImageVulnerabilitySummary_topRiskTier(ctx, field, obj) case "lastUpdated": out.Values[i] = ec._ImageVulnerabilitySummary_lastUpdated(ctx, field, obj) case "staleImageTag": @@ -5735,6 +5816,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/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index 9f98b965b..e4f3914ce 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -303,6 +303,15 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with risk tier MONITOR." priorityMonitor: Int! + "Number of vulnerabilities associated with known ransomware campaigns." + ransomwareCount: Int! + + "Number of vulnerabilities with a high EPSS percentile (≥ 0.90)." + highEpssCount: Int! + + "The highest risk tier among vulnerabilities in this summary." + topRiskTier: CVEPriority + "Timestamp of the last update of the vulnerability summary." lastUpdated: Time diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index 10eb6329b..72f1c811b 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -79,19 +79,22 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact imageName := parts[0] imageTag := parts[1] 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()), - ActNow: vulnFactor, - HighRisk: vulnFactor * 2, - ElevatedRisk: vulnFactor * 3, - Monitor: vulnFactor * 4, + 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, + RansomwareCount: vulnFactor, + HighEpssCount: vulnFactor * 2, + TopRiskTier: vulnerabilities.RiskTier_ACT_NOW, } if name == "no-errors" { diff --git a/internal/vulnerability/fake/v13s.go b/internal/vulnerability/fake/v13s.go index f623006bc..9939b1a0c 100644 --- a/internal/vulnerability/fake/v13s.go +++ b/internal/vulnerability/fake/v13s.go @@ -189,13 +189,21 @@ 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, + RansomwareCount: 1, + HighEpssCount: 2, + TopRiskTier: vulnerabilities.RiskTier_ACT_NOW, + BucketTime: timestamppb.New(time.Now()), }, }, } diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index e82214391..48b7cbfd9 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -76,19 +76,22 @@ 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"` - PriorityActNow int `json:"priorityActNow"` - PriorityHigh int `json:"priorityHigh"` - PriorityElevated int `json:"priorityElevated"` - PriorityMonitor int `json:"priorityMonitor"` + 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"` + PriorityActNow int `json:"priorityActNow"` + PriorityHigh int `json:"priorityHigh"` + PriorityElevated int `json:"priorityElevated"` + PriorityMonitor int `json:"priorityMonitor"` + RansomwareCount int `json:"ransomwareCount"` + HighEpssCount int `json:"highEpssCount"` + TopRiskTier *CVEPriority `json:"topRiskTier"` } type ImageVulnerabilityOrderField string diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index f72395647..e6cee3fcc 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -369,15 +369,22 @@ func GetImageVulnerabilitySummary(ctx context.Context, ref string) (*ImageVulner } return &ImageVulnerabilitySummary{ - Critical: int(sum.GetCritical()), - High: int(sum.GetHigh()), - Medium: int(sum.GetMedium()), - Low: int(sum.GetLow()), - Unassigned: int(sum.GetUnassigned()), - Total: int(sum.GetTotal()), - RiskScore: int(sum.GetRiskScore()), - LastUpdated: lastUpdated, - StaleImageTag: sum.StaleImageTag, + Critical: int(sum.GetCritical()), + High: int(sum.GetHigh()), + Medium: int(sum.GetMedium()), + Low: int(sum.GetLow()), + Unassigned: int(sum.GetUnassigned()), + Total: int(sum.GetTotal()), + RiskScore: int(sum.GetRiskScore()), + LastUpdated: lastUpdated, + StaleImageTag: sum.StaleImageTag, + PriorityActNow: int(sum.GetActNow()), + PriorityHigh: int(sum.GetHighRisk()), + PriorityElevated: int(sum.GetElevatedRisk()), + PriorityMonitor: int(sum.GetMonitor()), + RansomwareCount: int(sum.GetRansomwareCount()), + HighEpssCount: int(sum.GetHighEpssCount()), + TopRiskTier: mapRiskTier(sum.GetTopRiskTier()), }, nil } @@ -490,13 +497,20 @@ func getVulnerabilityHistory(ctx context.Context, opts []vulnerabilities.Option) for _, point := range resp.GetPoints() { samples = append(samples, &ImageVulnerabilitySample{ Summary: &ImageVulnerabilitySummary{ - Critical: int(point.GetCritical()), - High: int(point.GetHigh()), - Medium: int(point.GetMedium()), - Low: int(point.GetLow()), - Unassigned: int(point.GetUnassigned()), - Total: int(point.GetTotal()), - RiskScore: int(point.GetRiskScore()), + Critical: int(point.GetCritical()), + High: int(point.GetHigh()), + Medium: int(point.GetMedium()), + Low: int(point.GetLow()), + Unassigned: int(point.GetUnassigned()), + Total: int(point.GetTotal()), + RiskScore: int(point.GetRiskScore()), + PriorityActNow: int(point.GetActNow()), + PriorityHigh: int(point.GetHighRisk()), + PriorityElevated: int(point.GetElevatedRisk()), + PriorityMonitor: int(point.GetMonitor()), + RansomwareCount: int(point.GetRansomwareCount()), + HighEpssCount: int(point.GetHighEpssCount()), + TopRiskTier: mapRiskTier(point.GetTopRiskTier()), }, Date: point.GetBucketTime().AsTime(), }) diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 6aad4e9f3..8d1dac97d 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -90,6 +90,9 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa PriorityHigh: int(v13sSummary.HighRisk), PriorityElevated: int(v13sSummary.ElevatedRisk), PriorityMonitor: int(v13sSummary.Monitor), + RansomwareCount: int(v13sSummary.GetRansomwareCount()), + HighEpssCount: int(v13sSummary.GetHighEpssCount()), + TopRiskTier: mapRiskTier(v13sSummary.GetTopRiskTier()), } return &WorkloadVulnerabilitySummary{ @@ -156,3 +159,22 @@ func mapVulnerabilitySeverity(severity ImageVulnerabilitySeverity) vulnerabiliti return vulnerabilities.Severity_UNASSIGNED } } + +func mapRiskTier(tier vulnerabilities.RiskTier) *CVEPriority { + switch tier { + case vulnerabilities.RiskTier_ACT_NOW: + p := CVEPriorityImmediate + return &p + case vulnerabilities.RiskTier_HIGH_RISK: + p := CVEPriorityHigh + return &p + case vulnerabilities.RiskTier_ELEVATED_RISK: + p := CVEPriorityElevated + return &p + case vulnerabilities.RiskTier_MONITOR: + p := CVEPriorityMonitor + return &p + default: + return nil + } +} From 82b803ef44b36e55f2c97eea7f6da4c01ca0a86d Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:30:33 +0200 Subject: [PATCH 17/29] feat(vulnerability): add riskTier field to vulnerability summaries --- internal/graph/gengql/root_.generated.go | 5 +++++ internal/graph/gengql/vulnerability.generated.go | 9 ++++++++- internal/graph/schema/vulnerability.graphqls | 5 +++++ internal/vulnerability/models.go | 5 +++-- internal/vulnerability/queries.go | 4 ++++ internal/vulnerability/transform.go | 15 +++++++++++++++ 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index 2e68ebeb1..4fe4f0211 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -31658,6 +31658,11 @@ input TeamVulnerabilitySummaryFilter { """ environments: [String!] @deprecated(reason: "Use environmentName instead. Only one value is supported.") + + """ + Only return vulnerability summaries at or above the given risk tier. + """ + riskTier: CVEPriority } """ diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index 3431f820d..494f3012e 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -3469,7 +3469,7 @@ func (ec *executionContext) unmarshalInputTeamVulnerabilitySummaryFilter(ctx con asMap[k] = v } - fieldsInOrder := [...]string{"environmentName", "environments"} + fieldsInOrder := [...]string{"environmentName", "environments", "riskTier"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -3490,6 +3490,13 @@ func (ec *executionContext) unmarshalInputTeamVulnerabilitySummaryFilter(ctx con return it, err } it.Environments = data + case "riskTier": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("riskTier")) + data, err := ec.unmarshalOCVEPriority2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋvulnerabilityᚐCVEPriority(ctx, v) + if err != nil { + return it, err + } + it.RiskTier = data } } return it, nil diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index e4f3914ce..71a01101f 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -256,6 +256,11 @@ input TeamVulnerabilitySummaryFilter { """ environments: [String!] @deprecated(reason: "Use environmentName instead. Only one value is supported.") + + """ + Only return vulnerability summaries at or above the given risk tier. + """ + riskTier: CVEPriority } """ diff --git a/internal/vulnerability/models.go b/internal/vulnerability/models.go index 48b7cbfd9..960b677ea 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -390,8 +390,9 @@ func (e ImageVulnerabilitySuppressionState) MarshalGQL(w io.Writer) { } type TeamVulnerabilitySummaryFilter struct { - EnvironmentName *string `json:"environmentName,omitempty"` - Environments []string `json:"environments,omitempty"` + EnvironmentName *string `json:"environmentName,omitempty"` + Environments []string `json:"environments,omitempty"` + RiskTier *CVEPriority `json:"riskTier,omitempty"` } type ImageVulnerabilitySample struct { diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index e6cee3fcc..22e55c20d 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -74,6 +74,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.RiskTier != nil { + opts = append(opts, vulnerabilities.RiskTierFilter(mapCVEPriorityToRiskTier(*filter.RiskTier))) + } + if orderBy != nil { direction := vulnerabilities.Direction_ASC if orderBy.Direction == model.OrderDirectionDesc { diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 8d1dac97d..084e10877 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -178,3 +178,18 @@ func mapRiskTier(tier vulnerabilities.RiskTier) *CVEPriority { return nil } } + +func mapCVEPriorityToRiskTier(p CVEPriority) vulnerabilities.RiskTier { + switch p { + case CVEPriorityImmediate: + return vulnerabilities.RiskTier_ACT_NOW + case CVEPriorityHigh: + return vulnerabilities.RiskTier_HIGH_RISK + case CVEPriorityElevated: + return vulnerabilities.RiskTier_ELEVATED_RISK + case CVEPriorityMonitor: + return vulnerabilities.RiskTier_MONITOR + default: + return vulnerabilities.RiskTier_RISK_TIER_UNSPECIFIED + } +} From 3d83790db50eb58d201fb5e6a9b446c83a79f670 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:17:39 +0200 Subject: [PATCH 18/29] chore(deps): bump v13s api --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index b9d1d8f8d..fd15df87d 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-20260609085403-62cd3b4768b7 + github.com/nais/v13s/pkg/api v0.0.0-20260616093957-c9e73f2e0468 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.43.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.44.0 - google.golang.org/api v0.283.0 + google.golang.org/api v0.284.0 google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 @@ -465,7 +465,7 @@ require ( gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genai v1.54.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index a13fe6b14..bcf1ada19 100644 --- a/go.sum +++ b/go.sum @@ -524,8 +524,6 @@ 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= @@ -815,10 +813,10 @@ 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-20260604080807-5ff2f400c716 h1:FpEOQH7TP50xuVCkkcMk+ZaSRnxhmHZmuhDVPGL56sU= -github.com/nais/v13s/pkg/api v0.0.0-20260604080807-5ff2f400c716/go.mod h1:KBuEYLBJOFM36G7D5RAZ5oRyUv0/IOK9JCgkUS1eqqY= -github.com/nais/v13s/pkg/api v0.0.0-20260609085403-62cd3b4768b7 h1:mKyZ86EL7tvEaGuY6gj0IDmdYf1cKc2ujG0jer1RVpA= -github.com/nais/v13s/pkg/api v0.0.0-20260609085403-62cd3b4768b7/go.mod h1:D1ungKHGg4t242rWJXdusLiOs6clseEezpt1o1pU4aE= +github.com/nais/v13s/pkg/api v0.0.0-20260616070346-d983f8b497ce h1:RyffeqIrkGC3r5eklstBEMtrqVzMqLwao059X6ojEmk= +github.com/nais/v13s/pkg/api v0.0.0-20260616070346-d983f8b497ce/go.mod h1:D1ungKHGg4t242rWJXdusLiOs6clseEezpt1o1pU4aE= +github.com/nais/v13s/pkg/api v0.0.0-20260616093957-c9e73f2e0468 h1:tQR2pmEtny96qrf/vi7s7vvmb0FtjYSKpNwNy7mLyXo= +github.com/nais/v13s/pkg/api v0.0.0-20260616093957-c9e73f2e0468/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= @@ -1329,6 +1327,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/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= @@ -1420,10 +1420,10 @@ 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.283.0 h1:0lkp8u0MPwJVHqRL+nJlMAoZVVzbmiXmFHXMOTmSPik= google.golang.org/api v0.283.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= +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= @@ -1436,10 +1436,10 @@ google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgn google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= From b59e801ab89486e97c51c2c75e419d8ed217760d Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:18:04 +0200 Subject: [PATCH 19/29] feat(vulnerability): align CVE priority with v13s priority --- internal/vulnerability/fake/fakedata.go | 2 +- internal/vulnerability/fake/v13s.go | 2 +- internal/vulnerability/queries.go | 6 +- internal/vulnerability/transform.go | 81 ++++++++++++++++--------- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index 72f1c811b..3d22f55bb 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -94,7 +94,7 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact Monitor: vulnFactor * 4, RansomwareCount: vulnFactor, HighEpssCount: vulnFactor * 2, - TopRiskTier: vulnerabilities.RiskTier_ACT_NOW, + TopPriority: vulnerabilities.Priority_PRIORITY_ACT_NOW, } if name == "no-errors" { diff --git a/internal/vulnerability/fake/v13s.go b/internal/vulnerability/fake/v13s.go index 9939b1a0c..eba200710 100644 --- a/internal/vulnerability/fake/v13s.go +++ b/internal/vulnerability/fake/v13s.go @@ -202,7 +202,7 @@ func (f *fakeVulnerabilitiesClient) GetVulnerabilitySummaryTimeSeries(ctx contex Monitor: 4, RansomwareCount: 1, HighEpssCount: 2, - TopRiskTier: vulnerabilities.RiskTier_ACT_NOW, + TopPriority: vulnerabilities.Priority_PRIORITY_ACT_NOW, BucketTime: timestamppb.New(time.Now()), }, }, diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index 22e55c20d..6ecd6767b 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -75,7 +75,7 @@ func ListVulnerabilitySummaries(ctx context.Context, s slug.Slug, filter *TeamVu opts = append(opts, vulnerabilities.Limit(page.Limit())) if filter != nil && filter.RiskTier != nil { - opts = append(opts, vulnerabilities.RiskTierFilter(mapCVEPriorityToRiskTier(*filter.RiskTier))) + opts = append(opts, vulnerabilities.PriorityFilter(mapCVEPriorityToPriority(*filter.RiskTier))) } if orderBy != nil { @@ -388,7 +388,7 @@ func GetImageVulnerabilitySummary(ctx context.Context, ref string) (*ImageVulner PriorityMonitor: int(sum.GetMonitor()), RansomwareCount: int(sum.GetRansomwareCount()), HighEpssCount: int(sum.GetHighEpssCount()), - TopRiskTier: mapRiskTier(sum.GetTopRiskTier()), + TopRiskTier: mapPriority(sum.GetTopPriority()), }, nil } @@ -514,7 +514,7 @@ func getVulnerabilityHistory(ctx context.Context, opts []vulnerabilities.Option) PriorityMonitor: int(point.GetMonitor()), RansomwareCount: int(point.GetRansomwareCount()), HighEpssCount: int(point.GetHighEpssCount()), - TopRiskTier: mapRiskTier(point.GetTopRiskTier()), + TopRiskTier: mapPriority(point.GetTopPriority()), }, Date: point.GetBucketTime().AsTime(), }) diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 084e10877..70474d1d2 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -1,12 +1,14 @@ package vulnerability import ( + "strings" "time" "github.com/nais/api/internal/environmentmapper" "github.com/nais/api/internal/slug" "github.com/nais/api/internal/workload" "github.com/nais/v13s/pkg/api/vulnerabilities" + "google.golang.org/protobuf/reflect/protoreflect" ) func toImageVulnerability(v *vulnerabilities.Vulnerability) *ImageVulnerability { @@ -92,7 +94,7 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa PriorityMonitor: int(v13sSummary.Monitor), RansomwareCount: int(v13sSummary.GetRansomwareCount()), HighEpssCount: int(v13sSummary.GetHighEpssCount()), - TopRiskTier: mapRiskTier(v13sSummary.GetTopRiskTier()), + TopRiskTier: mapPriority(v13sSummary.GetTopPriority()), } return &WorkloadVulnerabilitySummary{ @@ -115,7 +117,7 @@ func toCVE(cve *vulnerabilities.Cve) *CVE { DetailsLink: cve.Link, CVSSScore: cve.CvssScore, Severity: ImageVulnerabilitySeverity(cve.Severity.String()), - Priority: parseCVEPriority(cve), + Priority: cvePriorityFromV13s(cve), EpssScore: cve.EpssScore, EpssPercentile: cve.EpssPercentile, HasKevEntry: cve.HasKevEntry, @@ -123,24 +125,47 @@ func toCVE(cve *vulnerabilities.Cve) *CVE { } } -func parseCVEPriority(cve *vulnerabilities.Cve) CVEPriority { +func cvePriorityFromV13s(cve *vulnerabilities.Cve) CVEPriority { if cve == nil { return CVEPriorityMonitor } - if cve.GetHasKevEntry() { - return CVEPriorityImmediate + if p, ok := cvePriorityFromEnumField(cve.ProtoReflect(), "risk_tier"); ok { + return p } - if cve.GetKnownRansomwareUse() || cve.GetEpssPercentile() >= 0.90 { - return CVEPriorityHigh + if p, ok := cvePriorityFromEnumField(cve.ProtoReflect(), "priority"); ok { + return p } - if (cve.GetSeverity() == vulnerabilities.Severity_CRITICAL || cve.GetSeverity() == vulnerabilities.Severity_HIGH) && cve.GetEpssPercentile() >= 0.50 { - return CVEPriorityElevated + return CVEPriorityMonitor +} + +func cvePriorityFromEnumField(msg protoreflect.Message, fieldName protoreflect.Name) (CVEPriority, bool) { + fd := msg.Descriptor().Fields().ByName(fieldName) + if fd == nil || fd.Kind() != protoreflect.EnumKind { + return CVEPriorityMonitor, false } - return CVEPriorityMonitor + enumValue := msg.Get(fd).Enum() + enumDescriptor := fd.Enum().Values().ByNumber(enumValue) + if enumDescriptor == nil { + return CVEPriorityMonitor, false + } + + enumName := strings.ToUpper(string(enumDescriptor.Name())) + switch { + case strings.Contains(enumName, "ACT_NOW"), strings.Contains(enumName, "IMMEDIATE"): + return CVEPriorityImmediate, true + case strings.Contains(enumName, "HIGH_RISK"), strings.Contains(enumName, "HIGH"): + return CVEPriorityHigh, true + case strings.Contains(enumName, "ELEVATED_RISK"), strings.Contains(enumName, "ELEVATED"): + return CVEPriorityElevated, true + case strings.Contains(enumName, "MONITOR"): + return CVEPriorityMonitor, true + default: + return CVEPriorityMonitor, false + } } func mapVulnerabilitySeverity(severity ImageVulnerabilitySeverity) vulnerabilities.Severity { @@ -160,36 +185,32 @@ func mapVulnerabilitySeverity(severity ImageVulnerabilitySeverity) vulnerabiliti } } -func mapRiskTier(tier vulnerabilities.RiskTier) *CVEPriority { - switch tier { - case vulnerabilities.RiskTier_ACT_NOW: - p := CVEPriorityImmediate - return &p - case vulnerabilities.RiskTier_HIGH_RISK: - p := CVEPriorityHigh - return &p - case vulnerabilities.RiskTier_ELEVATED_RISK: - p := CVEPriorityElevated - return &p - case vulnerabilities.RiskTier_MONITOR: - p := CVEPriorityMonitor - return &p +func mapPriority(priority vulnerabilities.Priority) *CVEPriority { + switch priority { + case vulnerabilities.Priority_PRIORITY_ACT_NOW: + return new(CVEPriorityImmediate) + case vulnerabilities.Priority_PRIORITY_HIGH: + return new(CVEPriorityHigh) + case vulnerabilities.Priority_PRIORITY_ELEVATED: + return new(CVEPriorityElevated) + case vulnerabilities.Priority_PRIORITY_MONITOR: + return new(CVEPriorityMonitor) default: return nil } } -func mapCVEPriorityToRiskTier(p CVEPriority) vulnerabilities.RiskTier { +func mapCVEPriorityToPriority(p CVEPriority) vulnerabilities.Priority { switch p { case CVEPriorityImmediate: - return vulnerabilities.RiskTier_ACT_NOW + return vulnerabilities.Priority_PRIORITY_ACT_NOW case CVEPriorityHigh: - return vulnerabilities.RiskTier_HIGH_RISK + return vulnerabilities.Priority_PRIORITY_HIGH case CVEPriorityElevated: - return vulnerabilities.RiskTier_ELEVATED_RISK + return vulnerabilities.Priority_PRIORITY_ELEVATED case CVEPriorityMonitor: - return vulnerabilities.RiskTier_MONITOR + return vulnerabilities.Priority_PRIORITY_MONITOR default: - return vulnerabilities.RiskTier_RISK_TIER_UNSPECIFIED + return vulnerabilities.Priority_PRIORITY_UNSPECIFIED } } From 008f0790432ac69487043901e695cb90e0a56ae5 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:18:54 +0200 Subject: [PATCH 20/29] fix(vulnerability): remove legacy risk-tier fallback --- internal/vulnerability/transform.go | 46 +++++++---------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 70474d1d2..970c8d644 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -1,14 +1,12 @@ package vulnerability import ( - "strings" "time" "github.com/nais/api/internal/environmentmapper" "github.com/nais/api/internal/slug" "github.com/nais/api/internal/workload" "github.com/nais/v13s/pkg/api/vulnerabilities" - "google.golang.org/protobuf/reflect/protoreflect" ) func toImageVulnerability(v *vulnerabilities.Vulnerability) *ImageVulnerability { @@ -130,41 +128,17 @@ func cvePriorityFromV13s(cve *vulnerabilities.Cve) CVEPriority { return CVEPriorityMonitor } - if p, ok := cvePriorityFromEnumField(cve.ProtoReflect(), "risk_tier"); ok { - return p - } - - if p, ok := cvePriorityFromEnumField(cve.ProtoReflect(), "priority"); ok { - return p - } - - return CVEPriorityMonitor -} - -func cvePriorityFromEnumField(msg protoreflect.Message, fieldName protoreflect.Name) (CVEPriority, bool) { - fd := msg.Descriptor().Fields().ByName(fieldName) - if fd == nil || fd.Kind() != protoreflect.EnumKind { - return CVEPriorityMonitor, false - } - - enumValue := msg.Get(fd).Enum() - enumDescriptor := fd.Enum().Values().ByNumber(enumValue) - if enumDescriptor == nil { - return CVEPriorityMonitor, false - } - - enumName := strings.ToUpper(string(enumDescriptor.Name())) - switch { - case strings.Contains(enumName, "ACT_NOW"), strings.Contains(enumName, "IMMEDIATE"): - return CVEPriorityImmediate, true - case strings.Contains(enumName, "HIGH_RISK"), strings.Contains(enumName, "HIGH"): - return CVEPriorityHigh, true - case strings.Contains(enumName, "ELEVATED_RISK"), strings.Contains(enumName, "ELEVATED"): - return CVEPriorityElevated, true - case strings.Contains(enumName, "MONITOR"): - return CVEPriorityMonitor, true + switch cve.GetPriority() { + case vulnerabilities.Priority_PRIORITY_ACT_NOW: + return CVEPriorityImmediate + case vulnerabilities.Priority_PRIORITY_HIGH: + return CVEPriorityHigh + case vulnerabilities.Priority_PRIORITY_ELEVATED: + return CVEPriorityElevated + case vulnerabilities.Priority_PRIORITY_MONITOR: + return CVEPriorityMonitor default: - return CVEPriorityMonitor, false + return CVEPriorityMonitor } } From c9398f10a717e81bf242d67980bdb9015487f01b Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:18:58 +0200 Subject: [PATCH 21/29] fix(issue): only flag immediate vulnerabilities --- internal/issue/checker/workload_v13s.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 5fc5f30ff..38f7b8bdf 100644 --- a/internal/issue/checker/workload_v13s.go +++ b/internal/issue/checker/workload_v13s.go @@ -186,23 +186,18 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { } summary := node.VulnerabilitySummary - if summary != nil && (summary.ActNow > 0 || summary.HighRisk > 0) { - severity := issue.SeverityWarning - if summary.ActNow > 0 { - severity = issue.SeverityCritical - } + 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: severity, + Severity: issue.SeverityCritical, Message: fmt.Sprintf( - "Image '%s' has %d IMMEDIATE and %d HIGH risk-tier vulnerabilities", + "Image '%s' has %d immediate vulnerabilities", node.Workload.ImageName, summary.ActNow, - summary.HighRisk, ), IssueDetails: issue.VulnerableImageIssueDetails{ Critical: int(summary.Critical), @@ -320,7 +315,7 @@ func (w Workload) vulnerabilities(ctx context.Context) []*Issue { Env: env, Severity: issue.SeverityCritical, Message: fmt.Sprintf( - "Workload with external ingresses %s has %d IMMEDIATE risk-tier vulnerabilities", + "Workload with external ingresses %s has %d immediate vulnerabilities", strings.Join(externalIngresses, ", "), node.VulnerabilitySummary.ActNow, ), From 07448e3e21b194c355385b320e3a597a601ae634 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:19:01 +0200 Subject: [PATCH 22/29] test(vulnerability): remove outdated CVE priority derivation tests --- internal/vulnerability/transform_test.go | 86 ------------------------ 1 file changed, 86 deletions(-) delete mode 100644 internal/vulnerability/transform_test.go diff --git a/internal/vulnerability/transform_test.go b/internal/vulnerability/transform_test.go deleted file mode 100644 index 3ecc35018..000000000 --- a/internal/vulnerability/transform_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package vulnerability - -import ( - "testing" - - "github.com/nais/v13s/pkg/api/vulnerabilities" -) - -func TestParseCVEPriority(t *testing.T) { - tests := []struct { - name string - cve *vulnerabilities.Cve - want CVEPriority - }{ - { - name: "nil cve defaults to monitor", - cve: nil, - want: CVEPriorityMonitor, - }, - { - name: "kev is immediate", - cve: &vulnerabilities.Cve{ - HasKevEntry: true, - KnownRansomwareUse: true, - EpssPercentile: new(0.99), - Severity: vulnerabilities.Severity_CRITICAL, - }, - want: CVEPriorityImmediate, - }, - { - name: "ransomware use is high", - cve: &vulnerabilities.Cve{ - KnownRansomwareUse: true, - }, - want: CVEPriorityHigh, - }, - { - name: "epss percentile threshold is high", - cve: &vulnerabilities.Cve{ - EpssPercentile: new(0.90), - }, - want: CVEPriorityHigh, - }, - { - name: "critical severity and elevated epss is elevated", - cve: &vulnerabilities.Cve{ - Severity: vulnerabilities.Severity_CRITICAL, - EpssPercentile: new(0.50), - }, - want: CVEPriorityElevated, - }, - { - name: "high severity and elevated epss is elevated", - cve: &vulnerabilities.Cve{ - Severity: vulnerabilities.Severity_HIGH, - EpssPercentile: new(0.50), - }, - want: CVEPriorityElevated, - }, - { - name: "low severity and elevated epss stays monitor", - cve: &vulnerabilities.Cve{ - Severity: vulnerabilities.Severity_LOW, - EpssPercentile: new(0.70), - }, - want: CVEPriorityMonitor, - }, - { - name: "high severity below elevated epss stays monitor", - cve: &vulnerabilities.Cve{ - Severity: vulnerabilities.Severity_HIGH, - EpssPercentile: new(0.49), - }, - want: CVEPriorityMonitor, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := parseCVEPriority(tt.cve) - if got != tt.want { - t.Fatalf("parseCVEPriority() = %s, want %s", got, tt.want) - } - }) - } -} From 6659e246ea5130dbd03830d9f9a7ff2529642722 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:28:43 +0200 Subject: [PATCH 23/29] feat(vulnerability): add riskTier field to vulnerability summaries --- integration_tests/issues_for_team.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/issues_for_team.lua b/integration_tests/issues_for_team.lua index 105c6369c..81658a02b 100644 --- a/integration_tests/issues_for_team.lua +++ b/integration_tests/issues_for_team.lua @@ -579,7 +579,7 @@ Test.gql("VulnerableImageIssue", function(t) nodes = { { __typename = "VulnerableImageIssue", - message = "Image 'vulnerable-image' has 2 IMMEDIATE and 3 HIGH risk-tier vulnerabilities", + message = "Image 'vulnerable-image' has 2 immediate vulnerabilities", severity = "CRITICAL", critical = 5, riskScore = 250, From 11fe780bdef8d9c25ed255b4f6fa52f6f27de05d Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:20:13 +0200 Subject: [PATCH 24/29] chore(deps): align v13s api with main --- go.mod | 2 +- go.sum | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index fd15df87d..7a6c925f3 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-20260616093957-c9e73f2e0468 + github.com/nais/v13s/pkg/api v0.0.0-20260616121454-101cee8c4b04 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 diff --git a/go.sum b/go.sum index bcf1ada19..df2938cff 100644 --- a/go.sum +++ b/go.sum @@ -813,10 +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-20260616070346-d983f8b497ce h1:RyffeqIrkGC3r5eklstBEMtrqVzMqLwao059X6ojEmk= -github.com/nais/v13s/pkg/api v0.0.0-20260616070346-d983f8b497ce/go.mod h1:D1ungKHGg4t242rWJXdusLiOs6clseEezpt1o1pU4aE= -github.com/nais/v13s/pkg/api v0.0.0-20260616093957-c9e73f2e0468 h1:tQR2pmEtny96qrf/vi7s7vvmb0FtjYSKpNwNy7mLyXo= -github.com/nais/v13s/pkg/api v0.0.0-20260616093957-c9e73f2e0468/go.mod h1:Ct3ihc4Qjjxt2h92Z+qttn0kkgtR8JQ7pmTF7PExH3s= +github.com/nais/v13s/pkg/api v0.0.0-20260616121454-101cee8c4b04 h1:NSwxZsfsm8kYlaP0iqEwyUXXS5HR692oEYti3QYXQ3U= +github.com/nais/v13s/pkg/api v0.0.0-20260616121454-101cee8c4b04/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= @@ -1325,8 +1323,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/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= @@ -1420,8 +1416,6 @@ 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.283.0 h1:0lkp8u0MPwJVHqRL+nJlMAoZVVzbmiXmFHXMOTmSPik= -google.golang.org/api v0.283.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= 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= @@ -1436,8 +1430,6 @@ google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgn google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= From 0786b7a0d4dee9eee36d6178f02ca9ccdd2a3d6b Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:48:42 +0200 Subject: [PATCH 25/29] fix(issue): support haproxy external ingress class detection --- .../testdata/dev-gcp/devteam/apps.yaml | 103 +++++++++++++++--- internal/issue/checker/workload_v13s.go | 12 +- internal/issue/checker/workload_v13s_test.go | 43 ++++++-- .../application/ingress_class_mapping.go | 14 +++ 4 files changed, 143 insertions(+), 29 deletions(-) diff --git a/internal/issue/checker/testdata/dev-gcp/devteam/apps.yaml b/internal/issue/checker/testdata/dev-gcp/devteam/apps.yaml index bf2eb704f..9b6818fbf 100644 --- a/internal/issue/checker/testdata/dev-gcp/devteam/apps.yaml +++ b/internal/issue/checker/testdata/dev-gcp/devteam/apps.yaml @@ -1,51 +1,118 @@ apiVersion: nais.io/v1alpha1 kind: Application metadata: - name: ext-app + name: ext-app-legacy spec: - image: ghcr.io/nais/ext-app:latest + image: ghcr.io/nais/ext-app-legacy:latest ingresses: - - https://ext.example.com - - https://internal.example.com + - https://legacy.external.example.com --- apiVersion: nais.io/v1alpha1 kind: Application metadata: - name: internal-only + name: ext-app-haproxy spec: - image: ghcr.io/nais/internal-only:latest + image: ghcr.io/nais/ext-app-haproxy:latest ingresses: - - https://internal-only.example.com + - https://haproxy.external.example.com +--- +apiVersion: nais.io/v1alpha1 +kind: Application +metadata: + name: ext-app-fa-haproxy +spec: + image: ghcr.io/nais/ext-app-fa-haproxy:latest + ingresses: + - https://haproxy.fa.external.example.com +--- +apiVersion: nais.io/v1alpha1 +kind: Application +metadata: + name: internal-only-haproxy +spec: + image: ghcr.io/nais/internal-only-haproxy:latest + ingresses: + - https://internal-only.haproxy.example.com +--- +apiVersion: nais.io/v1alpha1 +kind: Application +metadata: + name: unknown-class-ingress +spec: + image: ghcr.io/nais/unknown-class-ingress:latest + ingresses: + - https://unknown-class.example.com +--- +apiVersion: nais.io/v1alpha1 +kind: Application +metadata: + name: no-class-ingress +spec: + image: ghcr.io/nais/no-class-ingress:latest + ingresses: + - https://no-class.example.com --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: ext-app-external + name: ext-app-legacy-external labels: - app: ext-app + app: ext-app-legacy spec: ingressClassName: nais-ingress-external rules: - - host: ext.example.com + - host: legacy.external.example.com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ext-app-haproxy-external + labels: + app: ext-app-haproxy +spec: + ingressClassName: external-haproxy + rules: + - host: haproxy.external.example.com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ext-app-fa-haproxy-external + labels: + app: ext-app-fa-haproxy +spec: + ingressClassName: external-fa-haproxy + rules: + - host: haproxy.fa.external.example.com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: internal-only-haproxy-internal + labels: + app: internal-only-haproxy +spec: + ingressClassName: internal-haproxy + rules: + - host: internal-only.haproxy.example.com --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: ext-app-internal + name: unknown-class-ingress labels: - app: ext-app + app: unknown-class-ingress spec: - ingressClassName: nais-ingress + ingressClassName: unknown-ingress-class rules: - - host: internal.example.com + - host: unknown-class.example.com --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: internal-only-internal + name: no-class-ingress labels: - app: internal-only + app: no-class-ingress spec: - ingressClassName: nais-ingress rules: - - host: internal-only.example.com + - host: no-class.example.com diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 38f7b8bdf..864479eb3 100644 --- a/internal/issue/checker/workload_v13s.go +++ b/internal/issue/checker/workload_v13s.go @@ -8,13 +8,12 @@ import ( "github.com/nais/api/internal/environmentmapper" "github.com/nais/api/internal/issue" + "github.com/nais/api/internal/workload/application" "github.com/nais/v13s/pkg/api/vulnerabilities" - "k8s.io/utils/ptr" ) const ( - externalIngressClassName = "nais-ingress-external" - v13sQueryLimit = 69000 + v13sQueryLimit = 69000 ) type V13sClient interface { @@ -377,7 +376,12 @@ func (w Workload) externalIngressHostsByWorkload() map[string]map[string]struct{ ret := map[string]map[string]struct{}{} for _, ing := range w.IngressWatcher.All() { - if ptr.Deref(ing.Obj.Spec.IngressClassName, "") != externalIngressClassName { + ingressClassName := "" + if ing.Obj.Spec.IngressClassName != nil { + ingressClassName = *ing.Obj.Spec.IngressClassName + } + + if !application.IsIngressClassExternallyExposed(ingressClassName) { continue } diff --git a/internal/issue/checker/workload_v13s_test.go b/internal/issue/checker/workload_v13s_test.go index 7810737dd..5d67a70a4 100644 --- a/internal/issue/checker/workload_v13s_test.go +++ b/internal/issue/checker/workload_v13s_test.go @@ -28,6 +28,28 @@ func (s staticV13sClient) ListWorkloadsForVulnerability(ctx context.Context, vul } func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { + tests := []struct { + name string + workloadName string + expectedIngress string + wantIssue bool + }{ + {name: "legacy external ingress class", workloadName: "ext-app-legacy", expectedIngress: "https://legacy.external.example.com", wantIssue: true}, + {name: "external haproxy ingress class", workloadName: "ext-app-haproxy", expectedIngress: "https://haproxy.external.example.com", wantIssue: true}, + {name: "external authenticated haproxy ingress class", workloadName: "ext-app-fa-haproxy", expectedIngress: "https://haproxy.fa.external.example.com", wantIssue: true}, + {name: "internal haproxy ingress class", workloadName: "internal-only-haproxy", wantIssue: false}, + {name: "unknown ingress class", workloadName: "unknown-class-ingress", wantIssue: false}, + {name: "missing ingress class", workloadName: "no-class-ingress", wantIssue: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testVulnerabilitiesExternalIngressCriticalIssue(t, tt.workloadName, tt.expectedIngress, tt.wantIssue) + }) + } +} + +func testVulnerabilitiesExternalIngressCriticalIssue(t *testing.T, workloadName, expectedIngress string, wantIssue bool) { ctx := context.Background() scheme, err := kubernetes.NewScheme() @@ -60,19 +82,19 @@ func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { IngressWatcher: *ingressWatcher, V13sClient: staticV13sClient{workloads: []*vulnerabilities.WorkloadForVulnerability{ { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "ext-app"}, + WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: workloadName}, Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(10.0), Cve: &vulnerabilities.Cve{CvssScore: new(10.0)}}, }, { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "ext-app"}, + WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: workloadName}, Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(10.0), Cve: &vulnerabilities.Cve{CvssScore: new(10.0)}}, }, { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "internal-only"}, + WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "non-existing-workload"}, Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(10.0), Cve: &vulnerabilities.Cve{CvssScore: new(10.0)}}, }, { - WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: "ext-app"}, + WorkloadRef: &vulnerabilities.Workload{Cluster: "dev-gcp", Namespace: "devteam", Type: "app", Name: workloadName}, Vulnerability: &vulnerabilities.Vulnerability{CvssScore: new(9.9), Cve: &vulnerabilities.Cve{CvssScore: new(9.9)}}, }, }}, @@ -81,6 +103,13 @@ func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { issues := workload.vulnerabilities(ctx) + if !wantIssue { + if len(issues) != 0 { + t.Fatalf("expected 0 issues, got %d", len(issues)) + } + return + } + if len(issues) != 1 { t.Fatalf("expected 1 issue, got %d", len(issues)) } @@ -90,8 +119,8 @@ func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { t.Fatalf("expected issue type %s, got %s", issue.IssueTypeExternalIngressCriticalVulnerability, 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) @@ -103,7 +132,7 @@ func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { t.Fatalf("expected CVSS 10.0, got %v", details.CvssScore) } - 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/workload/application/ingress_class_mapping.go b/internal/workload/application/ingress_class_mapping.go index 880980038..eebeccc69 100644 --- a/internal/workload/application/ingress_class_mapping.go +++ b/internal/workload/application/ingress_class_mapping.go @@ -8,3 +8,17 @@ var ingressClassMapping = map[string]IngressType{ "external-haproxy": IngressTypeExternal, "external-fa-haproxy": IngressTypeAuthenticated, } + +// IsIngressClassExternallyExposed reports whether an ingress class represents +// external exposure of an application, including authenticated external +// ingress classes. +// +// Unknown or empty class names are treated as not externally exposed. +func IsIngressClassExternallyExposed(className string) bool { + ingressType, ok := ingressClassMapping[className] + if !ok { + return false + } + + return ingressType == IngressTypeExternal || ingressType == IngressTypeAuthenticated +} From 71fd1adf7aec0b254a7d2452756db47e2c1a2bce Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:30:39 +0200 Subject: [PATCH 26/29] refactor(vulnerability): hard-remove priority summary extras --- internal/graph/gengql/issues.generated.go | 322 ------------------ internal/graph/gengql/root_.generated.go | 145 ++------ internal/graph/gengql/schema.generated.go | 7 - .../graph/gengql/vulnerability.generated.go | 40 +-- internal/graph/issues.resolvers.go | 43 +-- internal/graph/schema/issues.graphqls | 14 +- internal/graph/schema/vulnerability.graphqls | 21 +- internal/issue/checker/workload_v13s.go | 98 ------ internal/issue/checker/workload_v13s_test.go | 82 +++-- internal/issue/model.go | 48 +-- internal/issue/queries.go | 9 - internal/vulnerability/fake/fakedata.go | 31 +- internal/vulnerability/fake/v13s.go | 29 +- internal/vulnerability/models.go | 31 +- internal/vulnerability/queries.go | 58 ++-- internal/vulnerability/transform.go | 29 +- 16 files changed, 212 insertions(+), 795 deletions(-) diff --git a/internal/graph/gengql/issues.generated.go b/internal/graph/gengql/issues.generated.go index f1077272d..561b6ba21 100644 --- a/internal/graph/gengql/issues.generated.go +++ b/internal/graph/gengql/issues.generated.go @@ -48,11 +48,6 @@ type ExternalIngressActNowVulnerabilityIssueResolver interface { Workload(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (workload.Workload, error) } -type ExternalIngressCriticalVulnerabilityIssueResolver interface { - TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (*team.TeamEnvironment, error) - - Workload(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (workload.Workload, error) -} type FailedSynchronizationIssueResolver interface { TeamEnvironment(ctx context.Context, obj *issue.FailedSynchronizationIssue) (*team.TeamEnvironment, error) @@ -791,185 +786,6 @@ func (ec *executionContext) fieldContext_ExternalIngressActNowVulnerabilityIssue return graphql.NewScalarFieldContext("ExternalIngressActNowVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) } -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_id(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type ID does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_teamEnvironment(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_teamEnvironment(ctx, field) - }, - func(ctx context.Context) (any, error) { - return ec.Resolvers.ExternalIngressCriticalVulnerabilityIssue().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_ExternalIngressCriticalVulnerabilityIssue_teamEnvironment(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ExternalIngressCriticalVulnerabilityIssue", - 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) _ExternalIngressCriticalVulnerabilityIssue_severity(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_severity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type Severity does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_message(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_workload(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_workload(ctx, field) - }, - func(ctx context.Context) (any, error) { - return ec.Resolvers.ExternalIngressCriticalVulnerabilityIssue().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_ExternalIngressCriticalVulnerabilityIssue_workload(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ExternalIngressCriticalVulnerabilityIssue", - 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) _ExternalIngressCriticalVulnerabilityIssue_cvssScore(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_cvssScore(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.CvssScore, nil - }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v float64) graphql.Marshaler { - return ec.marshalNFloat2float64(ctx, selections, v) - }, - true, - true, - ) -} -func (ec *executionContext) fieldContext_ExternalIngressCriticalVulnerabilityIssue_cvssScore(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type Float does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_ingresses(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_ingresses(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", 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, @@ -2989,13 +2805,6 @@ func (ec *executionContext) _Issue(ctx context.Context, sel ast.SelectionSet, ob return graphql.Null } return ec._FailedSynchronizationIssue(ctx, sel, obj) - case issue.ExternalIngressCriticalVulnerabilityIssue: - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, &obj) - case *issue.ExternalIngressCriticalVulnerabilityIssue: - if obj == nil { - return graphql.Null - } - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, obj) case issue.ExternalIngressActNowVulnerabilityIssue: return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, &obj) case *issue.ExternalIngressActNowVulnerabilityIssue: @@ -3551,137 +3360,6 @@ func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue(ctx context return out } -var externalIngressCriticalVulnerabilityIssueImplementors = []string{"ExternalIngressCriticalVulnerabilityIssue", "Issue", "Node"} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue(ctx context.Context, sel ast.SelectionSet, obj *issue.ExternalIngressCriticalVulnerabilityIssue) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, externalIngressCriticalVulnerabilityIssueImplementors) - - 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("ExternalIngressCriticalVulnerabilityIssue") - case "id": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_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._ExternalIngressCriticalVulnerabilityIssue_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._ExternalIngressCriticalVulnerabilityIssue_severity(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) - } - case "message": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_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._ExternalIngressCriticalVulnerabilityIssue_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 "cvssScore": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_cvssScore(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) - } - case "ingresses": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_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 4fe4f0211..7e755b204 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -82,7 +82,6 @@ type ResolverRoot interface { DeprecatedRegistryIssue() DeprecatedRegistryIssueResolver Environment() EnvironmentResolver ExternalIngressActNowVulnerabilityIssue() ExternalIngressActNowVulnerabilityIssueResolver - ExternalIngressCriticalVulnerabilityIssue() ExternalIngressCriticalVulnerabilityIssueResolver FailedSynchronizationIssue() FailedSynchronizationIssueResolver Ingress() IngressResolver IngressMetrics() IngressMetricsResolver @@ -951,16 +950,6 @@ type ComplexityRoot struct { Workload func(childComplexity int) int } - ExternalIngressCriticalVulnerabilityIssue struct { - CvssScore func(childComplexity int) int - ID func(childComplexity int) int - Ingresses func(childComplexity int) int - Message 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 @@ -1085,22 +1074,21 @@ type ComplexityRoot struct { } ImageVulnerabilitySummary struct { - Critical func(childComplexity int) int - High func(childComplexity int) int - HighEpssCount func(childComplexity int) int - LastUpdated func(childComplexity int) int - Low func(childComplexity int) int - Medium func(childComplexity int) int - PriorityActNow func(childComplexity int) int - PriorityElevated func(childComplexity int) int - PriorityHigh func(childComplexity int) int - PriorityMonitor func(childComplexity int) int - RansomwareCount func(childComplexity int) int - RiskScore func(childComplexity int) int - StaleImageTag func(childComplexity int) int - TopRiskTier func(childComplexity int) int - Total func(childComplexity int) int - Unassigned func(childComplexity int) int + Critical func(childComplexity int) int + High func(childComplexity int) int + HighEpssCount func(childComplexity int) int + LastUpdated func(childComplexity int) int + Low func(childComplexity int) int + Medium func(childComplexity int) int + PriorityActNow func(childComplexity int) int + PriorityElevated func(childComplexity int) int + PriorityHigh func(childComplexity int) int + PriorityMonitor func(childComplexity int) int + RiskScore func(childComplexity int) int + StaleImageTag func(childComplexity int) int + TopVulnerabilityPriority func(childComplexity int) int + Total func(childComplexity int) int + Unassigned func(childComplexity int) int } ImageVulnerabilitySuppression struct { @@ -6853,55 +6841,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Workload(childComplexity), true - case "ExternalIngressCriticalVulnerabilityIssue.cvssScore": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.CvssScore == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.CvssScore(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.id": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.ID == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.ID(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.ingresses": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Ingresses == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Ingresses(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.message": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Message == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Message(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.severity": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Severity == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Severity(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.teamEnvironment": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.TeamEnvironment == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.TeamEnvironment(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.workload": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Workload == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Workload(childComplexity), true - case "ExternalNetworkPolicyHost.ports": if e.ComplexityRoot.ExternalNetworkPolicyHost.Ports == nil { break @@ -7455,13 +7394,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.PriorityMonitor(childComplexity), true - case "ImageVulnerabilitySummary.ransomwareCount": - if e.ComplexityRoot.ImageVulnerabilitySummary.RansomwareCount == nil { - break - } - - return e.ComplexityRoot.ImageVulnerabilitySummary.RansomwareCount(childComplexity), true - case "ImageVulnerabilitySummary.riskScore": if e.ComplexityRoot.ImageVulnerabilitySummary.RiskScore == nil { break @@ -7476,12 +7408,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.StaleImageTag(childComplexity), true - case "ImageVulnerabilitySummary.topRiskTier": - if e.ComplexityRoot.ImageVulnerabilitySummary.TopRiskTier == nil { + case "ImageVulnerabilitySummary.topVulnerabilityPriority": + if e.ComplexityRoot.ImageVulnerabilitySummary.TopVulnerabilityPriority == nil { break } - return e.ComplexityRoot.ImageVulnerabilitySummary.TopRiskTier(childComplexity), true + return e.ComplexityRoot.ImageVulnerabilitySummary.TopVulnerabilityPriority(childComplexity), true case "ImageVulnerabilitySummary.total": if e.ComplexityRoot.ImageVulnerabilitySummary.Total == nil { @@ -23422,7 +23354,6 @@ enum IssueType { INVALID_SPEC MISSING_SBOM VULNERABLE_IMAGE - EXTERNAL_INGRESS_CRITICAL_VULNERABILITY EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." @@ -23440,18 +23371,7 @@ type VulnerableImageIssue implements Issue & Node { critical: Int! } -type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { - id: ID! - teamEnvironment: TeamEnvironment! - severity: Severity! - message: String! - - workload: Workload! - cvssScore: Float! - ingresses: [String!]! -} - -"Raised when a workload with external ingresses has one or more IMMEDIATE risk-tier vulnerabilities." +"Raised when a workload with external ingresses has one or more IMMEDIATE vulnerability-priority findings." type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! @@ -31660,7 +31580,7 @@ input TeamVulnerabilitySummaryFilter { @deprecated(reason: "Use environmentName instead. Only one value is supported.") """ - Only return vulnerability summaries at or above the given risk tier. + Only return vulnerability summaries at or above the given vulnerability priority. """ riskTier: CVEPriority } @@ -31698,26 +31618,23 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! - "Number of vulnerabilities with risk tier IMMEDIATE." + "Number of vulnerabilities with vulnerability priority IMMEDIATE." priorityActNow: Int! - "Number of vulnerabilities with risk tier HIGH." + "Number of vulnerabilities with vulnerability priority HIGH." priorityHigh: Int! - "Number of vulnerabilities with risk tier ELEVATED." + "Number of vulnerabilities with vulnerability priority ELEVATED." priorityElevated: Int! - "Number of vulnerabilities with risk tier MONITOR." + "Number of vulnerabilities with vulnerability priority MONITOR." priorityMonitor: Int! - "Number of vulnerabilities associated with known ransomware campaigns." - ransomwareCount: Int! - "Number of vulnerabilities with a high EPSS percentile (≥ 0.90)." highEpssCount: Int! - "The highest risk tier among vulnerabilities in this summary." - topRiskTier: CVEPriority + "The highest priority among vulnerabilities in this summary." + topVulnerabilityPriority: CVEPriority "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -32049,11 +31966,11 @@ enum VulnerabilitySummaryOrderByField { """ VULNERABILITY_SEVERITY_UNASSIGNED """ - Order by IMMEDIATE risk-tier count. + Order by IMMEDIATE vulnerability-priority count. """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by HIGH risk-tier count. + Order by HIGH vulnerability-priority count. """ VULNERABILITY_PRIORITY_HIGH } @@ -34187,12 +34104,10 @@ func (ec *executionContext) childFields_ImageVulnerabilitySummary(ctx context.Co return ec.fieldContext_ImageVulnerabilitySummary_priorityElevated(ctx, field) case "priorityMonitor": return ec.fieldContext_ImageVulnerabilitySummary_priorityMonitor(ctx, field) - case "ransomwareCount": - return ec.fieldContext_ImageVulnerabilitySummary_ransomwareCount(ctx, field) case "highEpssCount": return ec.fieldContext_ImageVulnerabilitySummary_highEpssCount(ctx, field) - case "topRiskTier": - return ec.fieldContext_ImageVulnerabilitySummary_topRiskTier(ctx, field) + case "topVulnerabilityPriority": + return ec.fieldContext_ImageVulnerabilitySummary_topVulnerabilityPriority(ctx, field) case "lastUpdated": return ec.fieldContext_ImageVulnerabilitySummary_lastUpdated(ctx, field) case "staleImageTag": diff --git a/internal/graph/gengql/schema.generated.go b/internal/graph/gengql/schema.generated.go index 1ee65c682..f5df61d16 100644 --- a/internal/graph/gengql/schema.generated.go +++ b/internal/graph/gengql/schema.generated.go @@ -6509,13 +6509,6 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._FailedSynchronizationIssue(ctx, sel, obj) - case issue.ExternalIngressCriticalVulnerabilityIssue: - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, &obj) - case *issue.ExternalIngressCriticalVulnerabilityIssue: - if obj == nil { - return graphql.Null - } - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, obj) case issue.ExternalIngressActNowVulnerabilityIssue: return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, &obj) case *issue.ExternalIngressActNowVulnerabilityIssue: diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index 494f3012e..b8dc0a17f 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -1642,29 +1642,6 @@ func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_priorityMonit return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_ransomwareCount(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_ransomwareCount(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.RansomwareCount, 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_ransomwareCount(_ 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_highEpssCount(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1688,16 +1665,16 @@ func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_highEpssCount return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_topRiskTier(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummary_topVulnerabilityPriority(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_topRiskTier(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummary_topVulnerabilityPriority(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.TopRiskTier, nil + return obj.TopVulnerabilityPriority, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v *vulnerability.CVEPriority) graphql.Marshaler { @@ -1707,7 +1684,7 @@ func (ec *executionContext) _ImageVulnerabilitySummary_topRiskTier(ctx context.C false, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_topRiskTier(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_topVulnerabilityPriority(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type CVEPriority does not have child fields")) } @@ -4411,18 +4388,13 @@ func (ec *executionContext) _ImageVulnerabilitySummary(ctx context.Context, sel if out.Values[i] == graphql.Null { out.Invalids++ } - case "ransomwareCount": - out.Values[i] = ec._ImageVulnerabilitySummary_ransomwareCount(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } case "highEpssCount": out.Values[i] = ec._ImageVulnerabilitySummary_highEpssCount(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "topRiskTier": - out.Values[i] = ec._ImageVulnerabilitySummary_topRiskTier(ctx, field, obj) + case "topVulnerabilityPriority": + out.Values[i] = ec._ImageVulnerabilitySummary_topVulnerabilityPriority(ctx, field, obj) case "lastUpdated": out.Values[i] = ec._ImageVulnerabilitySummary_lastUpdated(ctx, field, obj) case "staleImageTag": diff --git a/internal/graph/issues.resolvers.go b/internal/graph/issues.resolvers.go index c55a111de..314834394 100644 --- a/internal/graph/issues.resolvers.go +++ b/internal/graph/issues.resolvers.go @@ -48,14 +48,6 @@ func (r *externalIngressActNowVulnerabilityIssueResolver) Workload(ctx context.C return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) } -func (r *externalIngressCriticalVulnerabilityIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (*team.TeamEnvironment, error) { - return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) -} - -func (r *externalIngressCriticalVulnerabilityIssueResolver) Workload(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (workload.Workload, error) { - return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) -} - func (r *failedSynchronizationIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.FailedSynchronizationIssue) (*team.TeamEnvironment, error) { return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) } @@ -169,10 +161,6 @@ func (r *Resolver) ExternalIngressActNowVulnerabilityIssue() gengql.ExternalIngr return &externalIngressActNowVulnerabilityIssueResolver{r} } -func (r *Resolver) ExternalIngressCriticalVulnerabilityIssue() gengql.ExternalIngressCriticalVulnerabilityIssueResolver { - return &externalIngressCriticalVulnerabilityIssueResolver{r} -} - func (r *Resolver) FailedSynchronizationIssue() gengql.FailedSynchronizationIssueResolver { return &failedSynchronizationIssueResolver{r} } @@ -216,20 +204,19 @@ func (r *Resolver) VulnerableImageIssue() gengql.VulnerableImageIssueResolver { } type ( - applicationRestartLoopIssueResolver struct{ *Resolver } - deprecatedIngressIssueResolver struct{ *Resolver } - deprecatedRegistryIssueResolver struct{ *Resolver } - externalIngressActNowVulnerabilityIssueResolver struct{ *Resolver } - externalIngressCriticalVulnerabilityIssueResolver struct{ *Resolver } - failedSynchronizationIssueResolver struct{ *Resolver } - invalidSpecIssueResolver struct{ *Resolver } - lastRunFailedIssueResolver struct{ *Resolver } - missingSbomIssueResolver struct{ *Resolver } - noRunningInstancesIssueResolver struct{ *Resolver } - openSearchIssueResolver struct{ *Resolver } - sqlInstanceStateIssueResolver struct{ *Resolver } - sqlInstanceVersionIssueResolver struct{ *Resolver } - unleashReleaseChannelIssueResolver struct{ *Resolver } - valkeyIssueResolver struct{ *Resolver } - vulnerableImageIssueResolver struct{ *Resolver } + applicationRestartLoopIssueResolver struct{ *Resolver } + deprecatedIngressIssueResolver struct{ *Resolver } + deprecatedRegistryIssueResolver struct{ *Resolver } + externalIngressActNowVulnerabilityIssueResolver struct{ *Resolver } + failedSynchronizationIssueResolver struct{ *Resolver } + invalidSpecIssueResolver struct{ *Resolver } + lastRunFailedIssueResolver struct{ *Resolver } + missingSbomIssueResolver struct{ *Resolver } + noRunningInstancesIssueResolver struct{ *Resolver } + openSearchIssueResolver struct{ *Resolver } + sqlInstanceStateIssueResolver struct{ *Resolver } + sqlInstanceVersionIssueResolver struct{ *Resolver } + unleashReleaseChannelIssueResolver struct{ *Resolver } + valkeyIssueResolver struct{ *Resolver } + vulnerableImageIssueResolver struct{ *Resolver } ) diff --git a/internal/graph/schema/issues.graphqls b/internal/graph/schema/issues.graphqls index 11b41ee1f..54cb8fd89 100644 --- a/internal/graph/schema/issues.graphqls +++ b/internal/graph/schema/issues.graphqls @@ -163,7 +163,6 @@ enum IssueType { INVALID_SPEC MISSING_SBOM VULNERABLE_IMAGE - EXTERNAL_INGRESS_CRITICAL_VULNERABILITY EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." @@ -181,18 +180,7 @@ type VulnerableImageIssue implements Issue & Node { critical: Int! } -type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { - id: ID! - teamEnvironment: TeamEnvironment! - severity: Severity! - message: String! - - workload: Workload! - cvssScore: Float! - ingresses: [String!]! -} - -"Raised when a workload with external ingresses has one or more IMMEDIATE risk-tier vulnerabilities." +"Raised when a workload with external ingresses has one or more IMMEDIATE vulnerability-priority findings." type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index 71a01101f..f4735e633 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -258,7 +258,7 @@ input TeamVulnerabilitySummaryFilter { @deprecated(reason: "Use environmentName instead. Only one value is supported.") """ - Only return vulnerability summaries at or above the given risk tier. + Only return vulnerability summaries at or above the given vulnerability priority. """ riskTier: CVEPriority } @@ -296,26 +296,23 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! - "Number of vulnerabilities with risk tier IMMEDIATE." + "Number of vulnerabilities with vulnerability priority IMMEDIATE." priorityActNow: Int! - "Number of vulnerabilities with risk tier HIGH." + "Number of vulnerabilities with vulnerability priority HIGH." priorityHigh: Int! - "Number of vulnerabilities with risk tier ELEVATED." + "Number of vulnerabilities with vulnerability priority ELEVATED." priorityElevated: Int! - "Number of vulnerabilities with risk tier MONITOR." + "Number of vulnerabilities with vulnerability priority MONITOR." priorityMonitor: Int! - "Number of vulnerabilities associated with known ransomware campaigns." - ransomwareCount: Int! - "Number of vulnerabilities with a high EPSS percentile (≥ 0.90)." highEpssCount: Int! - "The highest risk tier among vulnerabilities in this summary." - topRiskTier: CVEPriority + "The highest priority among vulnerabilities in this summary." + topVulnerabilityPriority: CVEPriority "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -647,11 +644,11 @@ enum VulnerabilitySummaryOrderByField { """ VULNERABILITY_SEVERITY_UNASSIGNED """ - Order by IMMEDIATE risk-tier count. + Order by IMMEDIATE vulnerability-priority count. """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by HIGH risk-tier count. + Order by HIGH vulnerability-priority count. """ VULNERABILITY_PRIORITY_HIGH } diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 864479eb3..56e97b7f0 100644 --- a/internal/issue/checker/workload_v13s.go +++ b/internal/issue/checker/workload_v13s.go @@ -18,7 +18,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{} @@ -112,47 +111,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" { @@ -225,64 +183,8 @@ 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 { - continue - } - - workloadType, ok := mapType(workloadRef.GetType()) - if !ok || workloadType != issue.ResourceTypeApplication { - continue - } - - env := environmentmapper.EnvironmentName(workloadRef.GetCluster()) - key := workloadKey(env, workloadRef.GetNamespace(), workloadRef.GetName()) - if _, exists := seen[key]; exists { - continue - } - - externalIngresses := externalIngressesByWorkload[key] - if len(externalIngresses) == 0 { - continue - } - seen[key] = struct{}{} - - ret = append(ret, &Issue{ - IssueType: issue.IssueTypeExternalIngressCriticalVulnerability, - 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, - ), - IssueDetails: issue.ExternalIngressCriticalVulnerabilityIssueDetails{ - CvssScore: cvss, - Ingresses: externalIngresses, - }, - }) - } - seenActNow := map[string]struct{}{} for _, node := range resp.GetNodes() { workloadType, ok := mapType(node.Workload.GetType()) diff --git a/internal/issue/checker/workload_v13s_test.go b/internal/issue/checker/workload_v13s_test.go index 5d67a70a4..23055a450 100644 --- a/internal/issue/checker/workload_v13s_test.go +++ b/internal/issue/checker/workload_v13s_test.go @@ -16,23 +16,19 @@ 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_ExternalIngressCriticalIssue(t *testing.T) { +func TestVulnerabilities_ExternalIngressActNowIssue(t *testing.T) { tests := []struct { - name string - workloadName string - expectedIngress string - wantIssue bool + name string + workloadName string + expectedIngress string + wantIssue bool }{ {name: "legacy external ingress class", workloadName: "ext-app-legacy", expectedIngress: "https://legacy.external.example.com", wantIssue: true}, {name: "external haproxy ingress class", workloadName: "ext-app-haproxy", expectedIngress: "https://haproxy.external.example.com", wantIssue: true}, @@ -44,12 +40,12 @@ func TestVulnerabilities_ExternalIngressCriticalIssue(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - testVulnerabilitiesExternalIngressCriticalIssue(t, tt.workloadName, tt.expectedIngress, tt.wantIssue) + testVulnerabilitiesExternalIngressActNowIssue(t, tt.workloadName, tt.expectedIngress, tt.wantIssue) }) } } -func testVulnerabilitiesExternalIngressCriticalIssue(t *testing.T, workloadName, expectedIngress string, wantIssue bool) { +func testVulnerabilitiesExternalIngressActNowIssue(t *testing.T, workloadName, expectedIngress string, wantIssue bool) { ctx := context.Background() scheme, err := kubernetes.NewScheme() @@ -80,56 +76,76 @@ func testVulnerabilitiesExternalIngressCriticalIssue(t *testing.T, workloadName, 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: workloadName}, - 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: workloadName}, - 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: "non-existing-workload"}, - 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: workloadName}, - 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.IssueTypeExternalIngressActNowVulnerability { + actNowIssues = append(actNowIssues, issues[i]) + } + } if !wantIssue { - if len(issues) != 0 { - t.Fatalf("expected 0 issues, got %d", len(issues)) + 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.IssueTypeExternalIngressActNowVulnerability { + t.Fatalf("expected issue type %s, got %s", issue.IssueTypeExternalIngressActNowVulnerability, got.IssueType) } 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.ExternalIngressActNowVulnerabilityIssueDetails) 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.PriorityActNow != 2 { + t.Fatalf("expected priorityActNow 2, got %v", details.PriorityActNow) } if len(details.Ingresses) != 1 || details.Ingresses[0] != expectedIngress { diff --git a/internal/issue/model.go b/internal/issue/model.go index 500ca5c4a..0dd8ada84 100644 --- a/internal/issue/model.go +++ b/internal/issue/model.go @@ -183,11 +183,6 @@ type VulnerableImageIssueDetails struct { Critical int `json:"critical"` } -type ExternalIngressCriticalVulnerabilityIssueDetails struct { - CvssScore float64 `json:"cvssScore"` - Ingresses []string `json:"ingresses"` -} - type ExternalIngressActNowVulnerabilityIssueDetails struct { PriorityActNow int `json:"priorityActNow"` Ingresses []string `json:"ingresses"` @@ -196,22 +191,21 @@ type ExternalIngressActNowVulnerabilityIssueDetails struct { type IssueType string const ( - IssueTypeOpenSearch IssueType = "OPENSEARCH" - IssueTypeValkey IssueType = "VALKEY" - IssueTypeSqlInstanceState IssueType = "SQLINSTANCE_STATE" - IssueTypeSqlInstanceVersion IssueType = "SQLINSTANCE_VERSION" - IssueTypeDeprecatedIngress IssueType = "DEPRECATED_INGRESS" - IssueTypeDeprecatedRegistry IssueType = "DEPRECATED_REGISTRY" - IssueTypeNoRunningInstances IssueType = "NO_RUNNING_INSTANCES" - IssueTypeLastRunFailed IssueType = "LAST_RUN_FAILED" - IssueTypeFailedSynchronization IssueType = "FAILED_SYNCHRONIZATION" - IssueTypeInvalidSpec IssueType = "INVALID_SPEC" - IssueTypeVulnerableImage IssueType = "VULNERABLE_IMAGE" - IssueTypeMissingSBOM IssueType = "MISSING_SBOM" - IssueTypeExternalIngressCriticalVulnerability IssueType = "EXTERNAL_INGRESS_CRITICAL_VULNERABILITY" - IssueTypeExternalIngressActNowVulnerability IssueType = "EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY" - IssueTypeUnleashReleaseChannel IssueType = "UNLEASH_RELEASE_CHANNEL" - IssueTypeApplicationRestartLoop IssueType = "APPLICATION_RESTART_LOOP" + IssueTypeOpenSearch IssueType = "OPENSEARCH" + IssueTypeValkey IssueType = "VALKEY" + IssueTypeSqlInstanceState IssueType = "SQLINSTANCE_STATE" + IssueTypeSqlInstanceVersion IssueType = "SQLINSTANCE_VERSION" + IssueTypeDeprecatedIngress IssueType = "DEPRECATED_INGRESS" + IssueTypeDeprecatedRegistry IssueType = "DEPRECATED_REGISTRY" + IssueTypeNoRunningInstances IssueType = "NO_RUNNING_INSTANCES" + IssueTypeLastRunFailed IssueType = "LAST_RUN_FAILED" + IssueTypeFailedSynchronization IssueType = "FAILED_SYNCHRONIZATION" + IssueTypeInvalidSpec IssueType = "INVALID_SPEC" + IssueTypeVulnerableImage IssueType = "VULNERABLE_IMAGE" + IssueTypeMissingSBOM IssueType = "MISSING_SBOM" + IssueTypeExternalIngressActNowVulnerability IssueType = "EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY" + IssueTypeUnleashReleaseChannel IssueType = "UNLEASH_RELEASE_CHANNEL" + IssueTypeApplicationRestartLoop IssueType = "APPLICATION_RESTART_LOOP" ) var AllIssueType = []IssueType{ @@ -227,7 +221,6 @@ var AllIssueType = []IssueType{ IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, - IssueTypeExternalIngressCriticalVulnerability, IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop, @@ -235,7 +228,7 @@ var AllIssueType = []IssueType{ func (e IssueType) IsValid() bool { switch e { - case IssueTypeOpenSearch, IssueTypeValkey, IssueTypeSqlInstanceState, IssueTypeSqlInstanceVersion, IssueTypeDeprecatedIngress, IssueTypeDeprecatedRegistry, IssueTypeNoRunningInstances, IssueTypeLastRunFailed, IssueTypeInvalidSpec, IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressCriticalVulnerability, IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop: + case IssueTypeOpenSearch, IssueTypeValkey, IssueTypeSqlInstanceState, IssueTypeSqlInstanceVersion, IssueTypeDeprecatedIngress, IssueTypeDeprecatedRegistry, IssueTypeNoRunningInstances, IssueTypeLastRunFailed, IssueTypeInvalidSpec, IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop: return true } return false @@ -391,15 +384,6 @@ func (VulnerableImageIssue) IsIssue() {} func (VulnerableImageIssue) IsNode() {} -type ExternalIngressCriticalVulnerabilityIssue struct { - Base - ExternalIngressCriticalVulnerabilityIssueDetails -} - -func (ExternalIngressCriticalVulnerabilityIssue) IsIssue() {} - -func (ExternalIngressCriticalVulnerabilityIssue) IsNode() {} - type ExternalIngressActNowVulnerabilityIssue struct { Base ExternalIngressActNowVulnerabilityIssueDetails diff --git a/internal/issue/queries.go b/internal/issue/queries.go index b445b6878..f962ba05b 100644 --- a/internal/issue/queries.go +++ b/internal/issue/queries.go @@ -173,15 +173,6 @@ func convert(issue *issuesql.Issue) (Issue, error) { return &MissingSbomIssue{ Base: base, }, nil - case IssueTypeExternalIngressCriticalVulnerability: - d, err := unmarshal[ExternalIngressCriticalVulnerabilityIssueDetails](issue.IssueDetails) - if err != nil { - return nil, err - } - return &ExternalIngressCriticalVulnerabilityIssue{ - Base: base, - ExternalIngressCriticalVulnerabilityIssueDetails: *d, - }, nil case IssueTypeExternalIngressActNowVulnerability: d, err := unmarshal[ExternalIngressActNowVulnerabilityIssueDetails](issue.IssueDetails) if err != nil { diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index 3d22f55bb..59424def1 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -79,22 +79,21 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact imageName := parts[0] imageTag := parts[1] 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()), - ActNow: vulnFactor, - HighRisk: vulnFactor * 2, - ElevatedRisk: vulnFactor * 3, - Monitor: vulnFactor * 4, - RansomwareCount: vulnFactor, - HighEpssCount: vulnFactor * 2, - TopPriority: vulnerabilities.Priority_PRIORITY_ACT_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" { diff --git a/internal/vulnerability/fake/v13s.go b/internal/vulnerability/fake/v13s.go index eba200710..3e5480e44 100644 --- a/internal/vulnerability/fake/v13s.go +++ b/internal/vulnerability/fake/v13s.go @@ -189,21 +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, - RiskScore: 10, - ActNow: 1, - HighRisk: 2, - ElevatedRisk: 3, - Monitor: 4, - RansomwareCount: 1, - HighEpssCount: 2, - TopPriority: vulnerabilities.Priority_PRIORITY_ACT_NOW, - 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 960b677ea..76321ab07 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -76,22 +76,21 @@ 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"` - PriorityActNow int `json:"priorityActNow"` - PriorityHigh int `json:"priorityHigh"` - PriorityElevated int `json:"priorityElevated"` - PriorityMonitor int `json:"priorityMonitor"` - RansomwareCount int `json:"ransomwareCount"` - HighEpssCount int `json:"highEpssCount"` - TopRiskTier *CVEPriority `json:"topRiskTier"` + 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"` + PriorityActNow int `json:"priorityActNow"` + PriorityHigh int `json:"priorityHigh"` + PriorityElevated int `json:"priorityElevated"` + PriorityMonitor int `json:"priorityMonitor"` + HighEpssCount int `json:"highEpssCount"` + TopVulnerabilityPriority *CVEPriority `json:"topVulnerabilityPriority"` } type ImageVulnerabilityOrderField string diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index 6ecd6767b..c80a65a60 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -373,22 +373,21 @@ func GetImageVulnerabilitySummary(ctx context.Context, ref string) (*ImageVulner } return &ImageVulnerabilitySummary{ - Critical: int(sum.GetCritical()), - High: int(sum.GetHigh()), - Medium: int(sum.GetMedium()), - Low: int(sum.GetLow()), - Unassigned: int(sum.GetUnassigned()), - Total: int(sum.GetTotal()), - RiskScore: int(sum.GetRiskScore()), - LastUpdated: lastUpdated, - StaleImageTag: sum.StaleImageTag, - PriorityActNow: int(sum.GetActNow()), - PriorityHigh: int(sum.GetHighRisk()), - PriorityElevated: int(sum.GetElevatedRisk()), - PriorityMonitor: int(sum.GetMonitor()), - RansomwareCount: int(sum.GetRansomwareCount()), - HighEpssCount: int(sum.GetHighEpssCount()), - TopRiskTier: mapPriority(sum.GetTopPriority()), + Critical: int(sum.GetCritical()), + High: int(sum.GetHigh()), + Medium: int(sum.GetMedium()), + Low: int(sum.GetLow()), + Unassigned: int(sum.GetUnassigned()), + Total: int(sum.GetTotal()), + RiskScore: int(sum.GetRiskScore()), + LastUpdated: lastUpdated, + StaleImageTag: sum.StaleImageTag, + PriorityActNow: int(sum.GetActNow()), + PriorityHigh: int(sum.GetHighRisk()), + PriorityElevated: int(sum.GetElevatedRisk()), + PriorityMonitor: int(sum.GetMonitor()), + HighEpssCount: int(sum.GetHighEpssCount()), + TopVulnerabilityPriority: mapPriority(sum.GetTopPriority()), }, nil } @@ -501,20 +500,19 @@ func getVulnerabilityHistory(ctx context.Context, opts []vulnerabilities.Option) for _, point := range resp.GetPoints() { samples = append(samples, &ImageVulnerabilitySample{ Summary: &ImageVulnerabilitySummary{ - Critical: int(point.GetCritical()), - High: int(point.GetHigh()), - Medium: int(point.GetMedium()), - Low: int(point.GetLow()), - Unassigned: int(point.GetUnassigned()), - Total: int(point.GetTotal()), - RiskScore: int(point.GetRiskScore()), - PriorityActNow: int(point.GetActNow()), - PriorityHigh: int(point.GetHighRisk()), - PriorityElevated: int(point.GetElevatedRisk()), - PriorityMonitor: int(point.GetMonitor()), - RansomwareCount: int(point.GetRansomwareCount()), - HighEpssCount: int(point.GetHighEpssCount()), - TopRiskTier: mapPriority(point.GetTopPriority()), + Critical: int(point.GetCritical()), + High: int(point.GetHigh()), + Medium: int(point.GetMedium()), + Low: int(point.GetLow()), + Unassigned: int(point.GetUnassigned()), + Total: int(point.GetTotal()), + RiskScore: int(point.GetRiskScore()), + PriorityActNow: int(point.GetActNow()), + PriorityHigh: int(point.GetHighRisk()), + PriorityElevated: int(point.GetElevatedRisk()), + PriorityMonitor: int(point.GetMonitor()), + HighEpssCount: int(point.GetHighEpssCount()), + TopVulnerabilityPriority: mapPriority(point.GetTopPriority()), }, Date: point.GetBucketTime().AsTime(), }) diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 970c8d644..fb103a453 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -78,21 +78,20 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa } summary := &ImageVulnerabilitySummary{ - Critical: int(v13sSummary.Critical), - High: int(v13sSummary.High), - Medium: int(v13sSummary.Medium), - Low: int(v13sSummary.Low), - Unassigned: int(v13sSummary.Unassigned), - Total: int(v13sSummary.Total), - RiskScore: int(v13sSummary.RiskScore), - LastUpdated: lastUpdated, - PriorityActNow: int(v13sSummary.ActNow), - PriorityHigh: int(v13sSummary.HighRisk), - PriorityElevated: int(v13sSummary.ElevatedRisk), - PriorityMonitor: int(v13sSummary.Monitor), - RansomwareCount: int(v13sSummary.GetRansomwareCount()), - HighEpssCount: int(v13sSummary.GetHighEpssCount()), - TopRiskTier: mapPriority(v13sSummary.GetTopPriority()), + Critical: int(v13sSummary.Critical), + High: int(v13sSummary.High), + Medium: int(v13sSummary.Medium), + Low: int(v13sSummary.Low), + Unassigned: int(v13sSummary.Unassigned), + Total: int(v13sSummary.Total), + RiskScore: int(v13sSummary.RiskScore), + LastUpdated: lastUpdated, + PriorityActNow: int(v13sSummary.ActNow), + PriorityHigh: int(v13sSummary.HighRisk), + PriorityElevated: int(v13sSummary.ElevatedRisk), + PriorityMonitor: int(v13sSummary.Monitor), + HighEpssCount: int(v13sSummary.GetHighEpssCount()), + TopVulnerabilityPriority: mapPriority(v13sSummary.GetTopPriority()), } return &WorkloadVulnerabilitySummary{ From 575e0dbd54a67af702f6863ce8e2d7b5900aa86a Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:30:39 +0200 Subject: [PATCH 27/29] refactor(vulnerability): hard-remove priority summary extras --- internal/graph/gengql/issues.generated.go | 322 ------------------ internal/graph/gengql/root_.generated.go | 145 ++------ internal/graph/gengql/schema.generated.go | 7 - .../graph/gengql/vulnerability.generated.go | 40 +-- internal/graph/issues.resolvers.go | 43 +-- internal/graph/schema/issues.graphqls | 14 +- internal/graph/schema/vulnerability.graphqls | 21 +- internal/issue/checker/workload_v13s.go | 98 ------ internal/issue/checker/workload_v13s_test.go | 97 ++++-- internal/issue/model.go | 48 +-- internal/issue/queries.go | 9 - internal/vulnerability/fake/fakedata.go | 31 +- internal/vulnerability/fake/v13s.go | 29 +- internal/vulnerability/models.go | 31 +- internal/vulnerability/queries.go | 58 ++-- internal/vulnerability/transform.go | 29 +- 16 files changed, 234 insertions(+), 788 deletions(-) diff --git a/internal/graph/gengql/issues.generated.go b/internal/graph/gengql/issues.generated.go index f1077272d..561b6ba21 100644 --- a/internal/graph/gengql/issues.generated.go +++ b/internal/graph/gengql/issues.generated.go @@ -48,11 +48,6 @@ type ExternalIngressActNowVulnerabilityIssueResolver interface { Workload(ctx context.Context, obj *issue.ExternalIngressActNowVulnerabilityIssue) (workload.Workload, error) } -type ExternalIngressCriticalVulnerabilityIssueResolver interface { - TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (*team.TeamEnvironment, error) - - Workload(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (workload.Workload, error) -} type FailedSynchronizationIssueResolver interface { TeamEnvironment(ctx context.Context, obj *issue.FailedSynchronizationIssue) (*team.TeamEnvironment, error) @@ -791,185 +786,6 @@ func (ec *executionContext) fieldContext_ExternalIngressActNowVulnerabilityIssue return graphql.NewScalarFieldContext("ExternalIngressActNowVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) } -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_id(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type ID does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_teamEnvironment(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_teamEnvironment(ctx, field) - }, - func(ctx context.Context) (any, error) { - return ec.Resolvers.ExternalIngressCriticalVulnerabilityIssue().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_ExternalIngressCriticalVulnerabilityIssue_teamEnvironment(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ExternalIngressCriticalVulnerabilityIssue", - 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) _ExternalIngressCriticalVulnerabilityIssue_severity(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_severity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type Severity does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_message(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type String does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_workload(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_workload(ctx, field) - }, - func(ctx context.Context) (any, error) { - return ec.Resolvers.ExternalIngressCriticalVulnerabilityIssue().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_ExternalIngressCriticalVulnerabilityIssue_workload(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ExternalIngressCriticalVulnerabilityIssue", - 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) _ExternalIngressCriticalVulnerabilityIssue_cvssScore(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_cvssScore(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.CvssScore, nil - }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v float64) graphql.Marshaler { - return ec.marshalNFloat2float64(ctx, selections, v) - }, - true, - true, - ) -} -func (ec *executionContext) fieldContext_ExternalIngressCriticalVulnerabilityIssue_cvssScore(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", field, false, false, errors.New("field of type Float does not have child fields")) -} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue_ingresses(ctx context.Context, field graphql.CollectedField, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_ExternalIngressCriticalVulnerabilityIssue_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_ExternalIngressCriticalVulnerabilityIssue_ingresses(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("ExternalIngressCriticalVulnerabilityIssue", 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, @@ -2989,13 +2805,6 @@ func (ec *executionContext) _Issue(ctx context.Context, sel ast.SelectionSet, ob return graphql.Null } return ec._FailedSynchronizationIssue(ctx, sel, obj) - case issue.ExternalIngressCriticalVulnerabilityIssue: - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, &obj) - case *issue.ExternalIngressCriticalVulnerabilityIssue: - if obj == nil { - return graphql.Null - } - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, obj) case issue.ExternalIngressActNowVulnerabilityIssue: return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, &obj) case *issue.ExternalIngressActNowVulnerabilityIssue: @@ -3551,137 +3360,6 @@ func (ec *executionContext) _ExternalIngressActNowVulnerabilityIssue(ctx context return out } -var externalIngressCriticalVulnerabilityIssueImplementors = []string{"ExternalIngressCriticalVulnerabilityIssue", "Issue", "Node"} - -func (ec *executionContext) _ExternalIngressCriticalVulnerabilityIssue(ctx context.Context, sel ast.SelectionSet, obj *issue.ExternalIngressCriticalVulnerabilityIssue) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, externalIngressCriticalVulnerabilityIssueImplementors) - - 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("ExternalIngressCriticalVulnerabilityIssue") - case "id": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_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._ExternalIngressCriticalVulnerabilityIssue_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._ExternalIngressCriticalVulnerabilityIssue_severity(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) - } - case "message": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_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._ExternalIngressCriticalVulnerabilityIssue_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 "cvssScore": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_cvssScore(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) - } - case "ingresses": - out.Values[i] = ec._ExternalIngressCriticalVulnerabilityIssue_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 4fe4f0211..7e755b204 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -82,7 +82,6 @@ type ResolverRoot interface { DeprecatedRegistryIssue() DeprecatedRegistryIssueResolver Environment() EnvironmentResolver ExternalIngressActNowVulnerabilityIssue() ExternalIngressActNowVulnerabilityIssueResolver - ExternalIngressCriticalVulnerabilityIssue() ExternalIngressCriticalVulnerabilityIssueResolver FailedSynchronizationIssue() FailedSynchronizationIssueResolver Ingress() IngressResolver IngressMetrics() IngressMetricsResolver @@ -951,16 +950,6 @@ type ComplexityRoot struct { Workload func(childComplexity int) int } - ExternalIngressCriticalVulnerabilityIssue struct { - CvssScore func(childComplexity int) int - ID func(childComplexity int) int - Ingresses func(childComplexity int) int - Message 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 @@ -1085,22 +1074,21 @@ type ComplexityRoot struct { } ImageVulnerabilitySummary struct { - Critical func(childComplexity int) int - High func(childComplexity int) int - HighEpssCount func(childComplexity int) int - LastUpdated func(childComplexity int) int - Low func(childComplexity int) int - Medium func(childComplexity int) int - PriorityActNow func(childComplexity int) int - PriorityElevated func(childComplexity int) int - PriorityHigh func(childComplexity int) int - PriorityMonitor func(childComplexity int) int - RansomwareCount func(childComplexity int) int - RiskScore func(childComplexity int) int - StaleImageTag func(childComplexity int) int - TopRiskTier func(childComplexity int) int - Total func(childComplexity int) int - Unassigned func(childComplexity int) int + Critical func(childComplexity int) int + High func(childComplexity int) int + HighEpssCount func(childComplexity int) int + LastUpdated func(childComplexity int) int + Low func(childComplexity int) int + Medium func(childComplexity int) int + PriorityActNow func(childComplexity int) int + PriorityElevated func(childComplexity int) int + PriorityHigh func(childComplexity int) int + PriorityMonitor func(childComplexity int) int + RiskScore func(childComplexity int) int + StaleImageTag func(childComplexity int) int + TopVulnerabilityPriority func(childComplexity int) int + Total func(childComplexity int) int + Unassigned func(childComplexity int) int } ImageVulnerabilitySuppression struct { @@ -6853,55 +6841,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ExternalIngressActNowVulnerabilityIssue.Workload(childComplexity), true - case "ExternalIngressCriticalVulnerabilityIssue.cvssScore": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.CvssScore == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.CvssScore(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.id": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.ID == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.ID(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.ingresses": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Ingresses == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Ingresses(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.message": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Message == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Message(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.severity": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Severity == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Severity(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.teamEnvironment": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.TeamEnvironment == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.TeamEnvironment(childComplexity), true - - case "ExternalIngressCriticalVulnerabilityIssue.workload": - if e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Workload == nil { - break - } - - return e.ComplexityRoot.ExternalIngressCriticalVulnerabilityIssue.Workload(childComplexity), true - case "ExternalNetworkPolicyHost.ports": if e.ComplexityRoot.ExternalNetworkPolicyHost.Ports == nil { break @@ -7455,13 +7394,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.PriorityMonitor(childComplexity), true - case "ImageVulnerabilitySummary.ransomwareCount": - if e.ComplexityRoot.ImageVulnerabilitySummary.RansomwareCount == nil { - break - } - - return e.ComplexityRoot.ImageVulnerabilitySummary.RansomwareCount(childComplexity), true - case "ImageVulnerabilitySummary.riskScore": if e.ComplexityRoot.ImageVulnerabilitySummary.RiskScore == nil { break @@ -7476,12 +7408,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImageVulnerabilitySummary.StaleImageTag(childComplexity), true - case "ImageVulnerabilitySummary.topRiskTier": - if e.ComplexityRoot.ImageVulnerabilitySummary.TopRiskTier == nil { + case "ImageVulnerabilitySummary.topVulnerabilityPriority": + if e.ComplexityRoot.ImageVulnerabilitySummary.TopVulnerabilityPriority == nil { break } - return e.ComplexityRoot.ImageVulnerabilitySummary.TopRiskTier(childComplexity), true + return e.ComplexityRoot.ImageVulnerabilitySummary.TopVulnerabilityPriority(childComplexity), true case "ImageVulnerabilitySummary.total": if e.ComplexityRoot.ImageVulnerabilitySummary.Total == nil { @@ -23422,7 +23354,6 @@ enum IssueType { INVALID_SPEC MISSING_SBOM VULNERABLE_IMAGE - EXTERNAL_INGRESS_CRITICAL_VULNERABILITY EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." @@ -23440,18 +23371,7 @@ type VulnerableImageIssue implements Issue & Node { critical: Int! } -type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { - id: ID! - teamEnvironment: TeamEnvironment! - severity: Severity! - message: String! - - workload: Workload! - cvssScore: Float! - ingresses: [String!]! -} - -"Raised when a workload with external ingresses has one or more IMMEDIATE risk-tier vulnerabilities." +"Raised when a workload with external ingresses has one or more IMMEDIATE vulnerability-priority findings." type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! @@ -31660,7 +31580,7 @@ input TeamVulnerabilitySummaryFilter { @deprecated(reason: "Use environmentName instead. Only one value is supported.") """ - Only return vulnerability summaries at or above the given risk tier. + Only return vulnerability summaries at or above the given vulnerability priority. """ riskTier: CVEPriority } @@ -31698,26 +31618,23 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! - "Number of vulnerabilities with risk tier IMMEDIATE." + "Number of vulnerabilities with vulnerability priority IMMEDIATE." priorityActNow: Int! - "Number of vulnerabilities with risk tier HIGH." + "Number of vulnerabilities with vulnerability priority HIGH." priorityHigh: Int! - "Number of vulnerabilities with risk tier ELEVATED." + "Number of vulnerabilities with vulnerability priority ELEVATED." priorityElevated: Int! - "Number of vulnerabilities with risk tier MONITOR." + "Number of vulnerabilities with vulnerability priority MONITOR." priorityMonitor: Int! - "Number of vulnerabilities associated with known ransomware campaigns." - ransomwareCount: Int! - "Number of vulnerabilities with a high EPSS percentile (≥ 0.90)." highEpssCount: Int! - "The highest risk tier among vulnerabilities in this summary." - topRiskTier: CVEPriority + "The highest priority among vulnerabilities in this summary." + topVulnerabilityPriority: CVEPriority "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -32049,11 +31966,11 @@ enum VulnerabilitySummaryOrderByField { """ VULNERABILITY_SEVERITY_UNASSIGNED """ - Order by IMMEDIATE risk-tier count. + Order by IMMEDIATE vulnerability-priority count. """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by HIGH risk-tier count. + Order by HIGH vulnerability-priority count. """ VULNERABILITY_PRIORITY_HIGH } @@ -34187,12 +34104,10 @@ func (ec *executionContext) childFields_ImageVulnerabilitySummary(ctx context.Co return ec.fieldContext_ImageVulnerabilitySummary_priorityElevated(ctx, field) case "priorityMonitor": return ec.fieldContext_ImageVulnerabilitySummary_priorityMonitor(ctx, field) - case "ransomwareCount": - return ec.fieldContext_ImageVulnerabilitySummary_ransomwareCount(ctx, field) case "highEpssCount": return ec.fieldContext_ImageVulnerabilitySummary_highEpssCount(ctx, field) - case "topRiskTier": - return ec.fieldContext_ImageVulnerabilitySummary_topRiskTier(ctx, field) + case "topVulnerabilityPriority": + return ec.fieldContext_ImageVulnerabilitySummary_topVulnerabilityPriority(ctx, field) case "lastUpdated": return ec.fieldContext_ImageVulnerabilitySummary_lastUpdated(ctx, field) case "staleImageTag": diff --git a/internal/graph/gengql/schema.generated.go b/internal/graph/gengql/schema.generated.go index 1ee65c682..f5df61d16 100644 --- a/internal/graph/gengql/schema.generated.go +++ b/internal/graph/gengql/schema.generated.go @@ -6509,13 +6509,6 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._FailedSynchronizationIssue(ctx, sel, obj) - case issue.ExternalIngressCriticalVulnerabilityIssue: - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, &obj) - case *issue.ExternalIngressCriticalVulnerabilityIssue: - if obj == nil { - return graphql.Null - } - return ec._ExternalIngressCriticalVulnerabilityIssue(ctx, sel, obj) case issue.ExternalIngressActNowVulnerabilityIssue: return ec._ExternalIngressActNowVulnerabilityIssue(ctx, sel, &obj) case *issue.ExternalIngressActNowVulnerabilityIssue: diff --git a/internal/graph/gengql/vulnerability.generated.go b/internal/graph/gengql/vulnerability.generated.go index 494f3012e..b8dc0a17f 100644 --- a/internal/graph/gengql/vulnerability.generated.go +++ b/internal/graph/gengql/vulnerability.generated.go @@ -1642,29 +1642,6 @@ func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_priorityMonit return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_ransomwareCount(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_ransomwareCount(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.RansomwareCount, 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_ransomwareCount(_ 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_highEpssCount(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -1688,16 +1665,16 @@ func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_highEpssCount return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type Int does not have child fields")) } -func (ec *executionContext) _ImageVulnerabilitySummary_topRiskTier(ctx context.Context, field graphql.CollectedField, obj *vulnerability.ImageVulnerabilitySummary) (ret graphql.Marshaler) { +func (ec *executionContext) _ImageVulnerabilitySummary_topVulnerabilityPriority(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_topRiskTier(ctx, field) + return ec.fieldContext_ImageVulnerabilitySummary_topVulnerabilityPriority(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.TopRiskTier, nil + return obj.TopVulnerabilityPriority, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v *vulnerability.CVEPriority) graphql.Marshaler { @@ -1707,7 +1684,7 @@ func (ec *executionContext) _ImageVulnerabilitySummary_topRiskTier(ctx context.C false, ) } -func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_topRiskTier(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_ImageVulnerabilitySummary_topVulnerabilityPriority(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { return graphql.NewScalarFieldContext("ImageVulnerabilitySummary", field, false, false, errors.New("field of type CVEPriority does not have child fields")) } @@ -4411,18 +4388,13 @@ func (ec *executionContext) _ImageVulnerabilitySummary(ctx context.Context, sel if out.Values[i] == graphql.Null { out.Invalids++ } - case "ransomwareCount": - out.Values[i] = ec._ImageVulnerabilitySummary_ransomwareCount(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } case "highEpssCount": out.Values[i] = ec._ImageVulnerabilitySummary_highEpssCount(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "topRiskTier": - out.Values[i] = ec._ImageVulnerabilitySummary_topRiskTier(ctx, field, obj) + case "topVulnerabilityPriority": + out.Values[i] = ec._ImageVulnerabilitySummary_topVulnerabilityPriority(ctx, field, obj) case "lastUpdated": out.Values[i] = ec._ImageVulnerabilitySummary_lastUpdated(ctx, field, obj) case "staleImageTag": diff --git a/internal/graph/issues.resolvers.go b/internal/graph/issues.resolvers.go index c55a111de..314834394 100644 --- a/internal/graph/issues.resolvers.go +++ b/internal/graph/issues.resolvers.go @@ -48,14 +48,6 @@ func (r *externalIngressActNowVulnerabilityIssueResolver) Workload(ctx context.C return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) } -func (r *externalIngressCriticalVulnerabilityIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (*team.TeamEnvironment, error) { - return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) -} - -func (r *externalIngressCriticalVulnerabilityIssueResolver) Workload(ctx context.Context, obj *issue.ExternalIngressCriticalVulnerabilityIssue) (workload.Workload, error) { - return getWorkloadByResourceType(ctx, obj.TeamSlug, obj.EnvironmentName, obj.ResourceName, obj.ResourceType) -} - func (r *failedSynchronizationIssueResolver) TeamEnvironment(ctx context.Context, obj *issue.FailedSynchronizationIssue) (*team.TeamEnvironment, error) { return team.GetTeamEnvironment(ctx, obj.TeamSlug, obj.EnvironmentName) } @@ -169,10 +161,6 @@ func (r *Resolver) ExternalIngressActNowVulnerabilityIssue() gengql.ExternalIngr return &externalIngressActNowVulnerabilityIssueResolver{r} } -func (r *Resolver) ExternalIngressCriticalVulnerabilityIssue() gengql.ExternalIngressCriticalVulnerabilityIssueResolver { - return &externalIngressCriticalVulnerabilityIssueResolver{r} -} - func (r *Resolver) FailedSynchronizationIssue() gengql.FailedSynchronizationIssueResolver { return &failedSynchronizationIssueResolver{r} } @@ -216,20 +204,19 @@ func (r *Resolver) VulnerableImageIssue() gengql.VulnerableImageIssueResolver { } type ( - applicationRestartLoopIssueResolver struct{ *Resolver } - deprecatedIngressIssueResolver struct{ *Resolver } - deprecatedRegistryIssueResolver struct{ *Resolver } - externalIngressActNowVulnerabilityIssueResolver struct{ *Resolver } - externalIngressCriticalVulnerabilityIssueResolver struct{ *Resolver } - failedSynchronizationIssueResolver struct{ *Resolver } - invalidSpecIssueResolver struct{ *Resolver } - lastRunFailedIssueResolver struct{ *Resolver } - missingSbomIssueResolver struct{ *Resolver } - noRunningInstancesIssueResolver struct{ *Resolver } - openSearchIssueResolver struct{ *Resolver } - sqlInstanceStateIssueResolver struct{ *Resolver } - sqlInstanceVersionIssueResolver struct{ *Resolver } - unleashReleaseChannelIssueResolver struct{ *Resolver } - valkeyIssueResolver struct{ *Resolver } - vulnerableImageIssueResolver struct{ *Resolver } + applicationRestartLoopIssueResolver struct{ *Resolver } + deprecatedIngressIssueResolver struct{ *Resolver } + deprecatedRegistryIssueResolver struct{ *Resolver } + externalIngressActNowVulnerabilityIssueResolver struct{ *Resolver } + failedSynchronizationIssueResolver struct{ *Resolver } + invalidSpecIssueResolver struct{ *Resolver } + lastRunFailedIssueResolver struct{ *Resolver } + missingSbomIssueResolver struct{ *Resolver } + noRunningInstancesIssueResolver struct{ *Resolver } + openSearchIssueResolver struct{ *Resolver } + sqlInstanceStateIssueResolver struct{ *Resolver } + sqlInstanceVersionIssueResolver struct{ *Resolver } + unleashReleaseChannelIssueResolver struct{ *Resolver } + valkeyIssueResolver struct{ *Resolver } + vulnerableImageIssueResolver struct{ *Resolver } ) diff --git a/internal/graph/schema/issues.graphqls b/internal/graph/schema/issues.graphqls index 11b41ee1f..54cb8fd89 100644 --- a/internal/graph/schema/issues.graphqls +++ b/internal/graph/schema/issues.graphqls @@ -163,7 +163,6 @@ enum IssueType { INVALID_SPEC MISSING_SBOM VULNERABLE_IMAGE - EXTERNAL_INGRESS_CRITICAL_VULNERABILITY EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY UNLEASH_RELEASE_CHANNEL "Raised when an application is stuck in a restart loop." @@ -181,18 +180,7 @@ type VulnerableImageIssue implements Issue & Node { critical: Int! } -type ExternalIngressCriticalVulnerabilityIssue implements Issue & Node { - id: ID! - teamEnvironment: TeamEnvironment! - severity: Severity! - message: String! - - workload: Workload! - cvssScore: Float! - ingresses: [String!]! -} - -"Raised when a workload with external ingresses has one or more IMMEDIATE risk-tier vulnerabilities." +"Raised when a workload with external ingresses has one or more IMMEDIATE vulnerability-priority findings." type ExternalIngressActNowVulnerabilityIssue implements Issue & Node { id: ID! teamEnvironment: TeamEnvironment! diff --git a/internal/graph/schema/vulnerability.graphqls b/internal/graph/schema/vulnerability.graphqls index 71a01101f..f4735e633 100644 --- a/internal/graph/schema/vulnerability.graphqls +++ b/internal/graph/schema/vulnerability.graphqls @@ -258,7 +258,7 @@ input TeamVulnerabilitySummaryFilter { @deprecated(reason: "Use environmentName instead. Only one value is supported.") """ - Only return vulnerability summaries at or above the given risk tier. + Only return vulnerability summaries at or above the given vulnerability priority. """ riskTier: CVEPriority } @@ -296,26 +296,23 @@ type ImageVulnerabilitySummary { "Number of vulnerabilities with severity UNASSIGNED." unassigned: Int! - "Number of vulnerabilities with risk tier IMMEDIATE." + "Number of vulnerabilities with vulnerability priority IMMEDIATE." priorityActNow: Int! - "Number of vulnerabilities with risk tier HIGH." + "Number of vulnerabilities with vulnerability priority HIGH." priorityHigh: Int! - "Number of vulnerabilities with risk tier ELEVATED." + "Number of vulnerabilities with vulnerability priority ELEVATED." priorityElevated: Int! - "Number of vulnerabilities with risk tier MONITOR." + "Number of vulnerabilities with vulnerability priority MONITOR." priorityMonitor: Int! - "Number of vulnerabilities associated with known ransomware campaigns." - ransomwareCount: Int! - "Number of vulnerabilities with a high EPSS percentile (≥ 0.90)." highEpssCount: Int! - "The highest risk tier among vulnerabilities in this summary." - topRiskTier: CVEPriority + "The highest priority among vulnerabilities in this summary." + topVulnerabilityPriority: CVEPriority "Timestamp of the last update of the vulnerability summary." lastUpdated: Time @@ -647,11 +644,11 @@ enum VulnerabilitySummaryOrderByField { """ VULNERABILITY_SEVERITY_UNASSIGNED """ - Order by IMMEDIATE risk-tier count. + Order by IMMEDIATE vulnerability-priority count. """ VULNERABILITY_PRIORITY_ACT_NOW """ - Order by HIGH risk-tier count. + Order by HIGH vulnerability-priority count. """ VULNERABILITY_PRIORITY_HIGH } diff --git a/internal/issue/checker/workload_v13s.go b/internal/issue/checker/workload_v13s.go index 38f7b8bdf..0d13035de 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{} @@ -113,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" { @@ -226,64 +184,8 @@ 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 { - continue - } - - workloadType, ok := mapType(workloadRef.GetType()) - if !ok || workloadType != issue.ResourceTypeApplication { - continue - } - - env := environmentmapper.EnvironmentName(workloadRef.GetCluster()) - key := workloadKey(env, workloadRef.GetNamespace(), workloadRef.GetName()) - if _, exists := seen[key]; exists { - continue - } - - externalIngresses := externalIngressesByWorkload[key] - if len(externalIngresses) == 0 { - continue - } - seen[key] = struct{}{} - - ret = append(ret, &Issue{ - IssueType: issue.IssueTypeExternalIngressCriticalVulnerability, - 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, - ), - IssueDetails: issue.ExternalIngressCriticalVulnerabilityIssueDetails{ - CvssScore: cvss, - Ingresses: externalIngresses, - }, - }) - } - seenActNow := map[string]struct{}{} for _, node := range resp.GetNodes() { workloadType, ok := mapType(node.Workload.GetType()) diff --git a/internal/issue/checker/workload_v13s_test.go b/internal/issue/checker/workload_v13s_test.go index 7810737dd..23055a450 100644 --- a/internal/issue/checker/workload_v13s_test.go +++ b/internal/issue/checker/workload_v13s_test.go @@ -16,18 +16,36 @@ 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: "legacy external ingress class", workloadName: "ext-app-legacy", expectedIngress: "https://legacy.external.example.com", wantIssue: true}, + {name: "external haproxy ingress class", workloadName: "ext-app-haproxy", expectedIngress: "https://haproxy.external.example.com", wantIssue: true}, + {name: "external authenticated haproxy ingress class", workloadName: "ext-app-fa-haproxy", expectedIngress: "https://haproxy.fa.external.example.com", wantIssue: true}, + {name: "internal haproxy ingress class", workloadName: "internal-only-haproxy", wantIssue: false}, + {name: "unknown ingress class", workloadName: "unknown-class-ingress", wantIssue: false}, + {name: "missing ingress class", workloadName: "no-class-ingress", 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 +76,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.IssueTypeExternalIngressActNowVulnerability { + 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.IssueTypeExternalIngressActNowVulnerability { + t.Fatalf("expected issue type %s, got %s", issue.IssueTypeExternalIngressActNowVulnerability, 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.ExternalIngressActNowVulnerabilityIssueDetails) 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.PriorityActNow != 2 { + t.Fatalf("expected priorityActNow 2, got %v", details.PriorityActNow) } - 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 500ca5c4a..0dd8ada84 100644 --- a/internal/issue/model.go +++ b/internal/issue/model.go @@ -183,11 +183,6 @@ type VulnerableImageIssueDetails struct { Critical int `json:"critical"` } -type ExternalIngressCriticalVulnerabilityIssueDetails struct { - CvssScore float64 `json:"cvssScore"` - Ingresses []string `json:"ingresses"` -} - type ExternalIngressActNowVulnerabilityIssueDetails struct { PriorityActNow int `json:"priorityActNow"` Ingresses []string `json:"ingresses"` @@ -196,22 +191,21 @@ type ExternalIngressActNowVulnerabilityIssueDetails struct { type IssueType string const ( - IssueTypeOpenSearch IssueType = "OPENSEARCH" - IssueTypeValkey IssueType = "VALKEY" - IssueTypeSqlInstanceState IssueType = "SQLINSTANCE_STATE" - IssueTypeSqlInstanceVersion IssueType = "SQLINSTANCE_VERSION" - IssueTypeDeprecatedIngress IssueType = "DEPRECATED_INGRESS" - IssueTypeDeprecatedRegistry IssueType = "DEPRECATED_REGISTRY" - IssueTypeNoRunningInstances IssueType = "NO_RUNNING_INSTANCES" - IssueTypeLastRunFailed IssueType = "LAST_RUN_FAILED" - IssueTypeFailedSynchronization IssueType = "FAILED_SYNCHRONIZATION" - IssueTypeInvalidSpec IssueType = "INVALID_SPEC" - IssueTypeVulnerableImage IssueType = "VULNERABLE_IMAGE" - IssueTypeMissingSBOM IssueType = "MISSING_SBOM" - IssueTypeExternalIngressCriticalVulnerability IssueType = "EXTERNAL_INGRESS_CRITICAL_VULNERABILITY" - IssueTypeExternalIngressActNowVulnerability IssueType = "EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY" - IssueTypeUnleashReleaseChannel IssueType = "UNLEASH_RELEASE_CHANNEL" - IssueTypeApplicationRestartLoop IssueType = "APPLICATION_RESTART_LOOP" + IssueTypeOpenSearch IssueType = "OPENSEARCH" + IssueTypeValkey IssueType = "VALKEY" + IssueTypeSqlInstanceState IssueType = "SQLINSTANCE_STATE" + IssueTypeSqlInstanceVersion IssueType = "SQLINSTANCE_VERSION" + IssueTypeDeprecatedIngress IssueType = "DEPRECATED_INGRESS" + IssueTypeDeprecatedRegistry IssueType = "DEPRECATED_REGISTRY" + IssueTypeNoRunningInstances IssueType = "NO_RUNNING_INSTANCES" + IssueTypeLastRunFailed IssueType = "LAST_RUN_FAILED" + IssueTypeFailedSynchronization IssueType = "FAILED_SYNCHRONIZATION" + IssueTypeInvalidSpec IssueType = "INVALID_SPEC" + IssueTypeVulnerableImage IssueType = "VULNERABLE_IMAGE" + IssueTypeMissingSBOM IssueType = "MISSING_SBOM" + IssueTypeExternalIngressActNowVulnerability IssueType = "EXTERNAL_INGRESS_ACT_NOW_VULNERABILITY" + IssueTypeUnleashReleaseChannel IssueType = "UNLEASH_RELEASE_CHANNEL" + IssueTypeApplicationRestartLoop IssueType = "APPLICATION_RESTART_LOOP" ) var AllIssueType = []IssueType{ @@ -227,7 +221,6 @@ var AllIssueType = []IssueType{ IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, - IssueTypeExternalIngressCriticalVulnerability, IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop, @@ -235,7 +228,7 @@ var AllIssueType = []IssueType{ func (e IssueType) IsValid() bool { switch e { - case IssueTypeOpenSearch, IssueTypeValkey, IssueTypeSqlInstanceState, IssueTypeSqlInstanceVersion, IssueTypeDeprecatedIngress, IssueTypeDeprecatedRegistry, IssueTypeNoRunningInstances, IssueTypeLastRunFailed, IssueTypeInvalidSpec, IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressCriticalVulnerability, IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop: + case IssueTypeOpenSearch, IssueTypeValkey, IssueTypeSqlInstanceState, IssueTypeSqlInstanceVersion, IssueTypeDeprecatedIngress, IssueTypeDeprecatedRegistry, IssueTypeNoRunningInstances, IssueTypeLastRunFailed, IssueTypeInvalidSpec, IssueTypeFailedSynchronization, IssueTypeVulnerableImage, IssueTypeMissingSBOM, IssueTypeExternalIngressActNowVulnerability, IssueTypeUnleashReleaseChannel, IssueTypeApplicationRestartLoop: return true } return false @@ -391,15 +384,6 @@ func (VulnerableImageIssue) IsIssue() {} func (VulnerableImageIssue) IsNode() {} -type ExternalIngressCriticalVulnerabilityIssue struct { - Base - ExternalIngressCriticalVulnerabilityIssueDetails -} - -func (ExternalIngressCriticalVulnerabilityIssue) IsIssue() {} - -func (ExternalIngressCriticalVulnerabilityIssue) IsNode() {} - type ExternalIngressActNowVulnerabilityIssue struct { Base ExternalIngressActNowVulnerabilityIssueDetails diff --git a/internal/issue/queries.go b/internal/issue/queries.go index b445b6878..f962ba05b 100644 --- a/internal/issue/queries.go +++ b/internal/issue/queries.go @@ -173,15 +173,6 @@ func convert(issue *issuesql.Issue) (Issue, error) { return &MissingSbomIssue{ Base: base, }, nil - case IssueTypeExternalIngressCriticalVulnerability: - d, err := unmarshal[ExternalIngressCriticalVulnerabilityIssueDetails](issue.IssueDetails) - if err != nil { - return nil, err - } - return &ExternalIngressCriticalVulnerabilityIssue{ - Base: base, - ExternalIngressCriticalVulnerabilityIssueDetails: *d, - }, nil case IssueTypeExternalIngressActNowVulnerability: d, err := unmarshal[ExternalIngressActNowVulnerabilityIssueDetails](issue.IssueDetails) if err != nil { diff --git a/internal/vulnerability/fake/fakedata.go b/internal/vulnerability/fake/fakedata.go index 3d22f55bb..59424def1 100644 --- a/internal/vulnerability/fake/fakedata.go +++ b/internal/vulnerability/fake/fakedata.go @@ -79,22 +79,21 @@ func createWorkloadSummary(env, team, workloadType, name, image string, vulnFact imageName := parts[0] imageTag := parts[1] 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()), - ActNow: vulnFactor, - HighRisk: vulnFactor * 2, - ElevatedRisk: vulnFactor * 3, - Monitor: vulnFactor * 4, - RansomwareCount: vulnFactor, - HighEpssCount: vulnFactor * 2, - TopPriority: vulnerabilities.Priority_PRIORITY_ACT_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" { diff --git a/internal/vulnerability/fake/v13s.go b/internal/vulnerability/fake/v13s.go index eba200710..3e5480e44 100644 --- a/internal/vulnerability/fake/v13s.go +++ b/internal/vulnerability/fake/v13s.go @@ -189,21 +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, - RiskScore: 10, - ActNow: 1, - HighRisk: 2, - ElevatedRisk: 3, - Monitor: 4, - RansomwareCount: 1, - HighEpssCount: 2, - TopPriority: vulnerabilities.Priority_PRIORITY_ACT_NOW, - 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 960b677ea..76321ab07 100644 --- a/internal/vulnerability/models.go +++ b/internal/vulnerability/models.go @@ -76,22 +76,21 @@ 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"` - PriorityActNow int `json:"priorityActNow"` - PriorityHigh int `json:"priorityHigh"` - PriorityElevated int `json:"priorityElevated"` - PriorityMonitor int `json:"priorityMonitor"` - RansomwareCount int `json:"ransomwareCount"` - HighEpssCount int `json:"highEpssCount"` - TopRiskTier *CVEPriority `json:"topRiskTier"` + 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"` + PriorityActNow int `json:"priorityActNow"` + PriorityHigh int `json:"priorityHigh"` + PriorityElevated int `json:"priorityElevated"` + PriorityMonitor int `json:"priorityMonitor"` + HighEpssCount int `json:"highEpssCount"` + TopVulnerabilityPriority *CVEPriority `json:"topVulnerabilityPriority"` } type ImageVulnerabilityOrderField string diff --git a/internal/vulnerability/queries.go b/internal/vulnerability/queries.go index 6ecd6767b..c80a65a60 100644 --- a/internal/vulnerability/queries.go +++ b/internal/vulnerability/queries.go @@ -373,22 +373,21 @@ func GetImageVulnerabilitySummary(ctx context.Context, ref string) (*ImageVulner } return &ImageVulnerabilitySummary{ - Critical: int(sum.GetCritical()), - High: int(sum.GetHigh()), - Medium: int(sum.GetMedium()), - Low: int(sum.GetLow()), - Unassigned: int(sum.GetUnassigned()), - Total: int(sum.GetTotal()), - RiskScore: int(sum.GetRiskScore()), - LastUpdated: lastUpdated, - StaleImageTag: sum.StaleImageTag, - PriorityActNow: int(sum.GetActNow()), - PriorityHigh: int(sum.GetHighRisk()), - PriorityElevated: int(sum.GetElevatedRisk()), - PriorityMonitor: int(sum.GetMonitor()), - RansomwareCount: int(sum.GetRansomwareCount()), - HighEpssCount: int(sum.GetHighEpssCount()), - TopRiskTier: mapPriority(sum.GetTopPriority()), + Critical: int(sum.GetCritical()), + High: int(sum.GetHigh()), + Medium: int(sum.GetMedium()), + Low: int(sum.GetLow()), + Unassigned: int(sum.GetUnassigned()), + Total: int(sum.GetTotal()), + RiskScore: int(sum.GetRiskScore()), + LastUpdated: lastUpdated, + StaleImageTag: sum.StaleImageTag, + PriorityActNow: int(sum.GetActNow()), + PriorityHigh: int(sum.GetHighRisk()), + PriorityElevated: int(sum.GetElevatedRisk()), + PriorityMonitor: int(sum.GetMonitor()), + HighEpssCount: int(sum.GetHighEpssCount()), + TopVulnerabilityPriority: mapPriority(sum.GetTopPriority()), }, nil } @@ -501,20 +500,19 @@ func getVulnerabilityHistory(ctx context.Context, opts []vulnerabilities.Option) for _, point := range resp.GetPoints() { samples = append(samples, &ImageVulnerabilitySample{ Summary: &ImageVulnerabilitySummary{ - Critical: int(point.GetCritical()), - High: int(point.GetHigh()), - Medium: int(point.GetMedium()), - Low: int(point.GetLow()), - Unassigned: int(point.GetUnassigned()), - Total: int(point.GetTotal()), - RiskScore: int(point.GetRiskScore()), - PriorityActNow: int(point.GetActNow()), - PriorityHigh: int(point.GetHighRisk()), - PriorityElevated: int(point.GetElevatedRisk()), - PriorityMonitor: int(point.GetMonitor()), - RansomwareCount: int(point.GetRansomwareCount()), - HighEpssCount: int(point.GetHighEpssCount()), - TopRiskTier: mapPriority(point.GetTopPriority()), + Critical: int(point.GetCritical()), + High: int(point.GetHigh()), + Medium: int(point.GetMedium()), + Low: int(point.GetLow()), + Unassigned: int(point.GetUnassigned()), + Total: int(point.GetTotal()), + RiskScore: int(point.GetRiskScore()), + PriorityActNow: int(point.GetActNow()), + PriorityHigh: int(point.GetHighRisk()), + PriorityElevated: int(point.GetElevatedRisk()), + PriorityMonitor: int(point.GetMonitor()), + HighEpssCount: int(point.GetHighEpssCount()), + TopVulnerabilityPriority: mapPriority(point.GetTopPriority()), }, Date: point.GetBucketTime().AsTime(), }) diff --git a/internal/vulnerability/transform.go b/internal/vulnerability/transform.go index 970c8d644..fb103a453 100644 --- a/internal/vulnerability/transform.go +++ b/internal/vulnerability/transform.go @@ -78,21 +78,20 @@ func toWorkloadVulnerabilitySummary(w *vulnerabilities.WorkloadSummary) *Workloa } summary := &ImageVulnerabilitySummary{ - Critical: int(v13sSummary.Critical), - High: int(v13sSummary.High), - Medium: int(v13sSummary.Medium), - Low: int(v13sSummary.Low), - Unassigned: int(v13sSummary.Unassigned), - Total: int(v13sSummary.Total), - RiskScore: int(v13sSummary.RiskScore), - LastUpdated: lastUpdated, - PriorityActNow: int(v13sSummary.ActNow), - PriorityHigh: int(v13sSummary.HighRisk), - PriorityElevated: int(v13sSummary.ElevatedRisk), - PriorityMonitor: int(v13sSummary.Monitor), - RansomwareCount: int(v13sSummary.GetRansomwareCount()), - HighEpssCount: int(v13sSummary.GetHighEpssCount()), - TopRiskTier: mapPriority(v13sSummary.GetTopPriority()), + Critical: int(v13sSummary.Critical), + High: int(v13sSummary.High), + Medium: int(v13sSummary.Medium), + Low: int(v13sSummary.Low), + Unassigned: int(v13sSummary.Unassigned), + Total: int(v13sSummary.Total), + RiskScore: int(v13sSummary.RiskScore), + LastUpdated: lastUpdated, + PriorityActNow: int(v13sSummary.ActNow), + PriorityHigh: int(v13sSummary.HighRisk), + PriorityElevated: int(v13sSummary.ElevatedRisk), + PriorityMonitor: int(v13sSummary.Monitor), + HighEpssCount: int(v13sSummary.GetHighEpssCount()), + TopVulnerabilityPriority: mapPriority(v13sSummary.GetTopPriority()), } return &WorkloadVulnerabilitySummary{ From f112eaeec49116678666931fd11829ad34f1da51 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:39:41 +0200 Subject: [PATCH 28/29] chore(deps): bump v13s api to latest main --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7a6c925f3..f2a0c9b72 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-20260616121454-101cee8c4b04 + 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 diff --git a/go.sum b/go.sum index df2938cff..0a31e6531 100644 --- a/go.sum +++ b/go.sum @@ -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-20260616121454-101cee8c4b04 h1:NSwxZsfsm8kYlaP0iqEwyUXXS5HR692oEYti3QYXQ3U= -github.com/nais/v13s/pkg/api v0.0.0-20260616121454-101cee8c4b04/go.mod h1:Ct3ihc4Qjjxt2h92Z+qttn0kkgtR8JQ7pmTF7PExH3s= +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= From 4828624a2ec444b3c54e30c0bf6c97cb8011ede9 Mon Sep 17 00:00:00 2001 From: Youssef Bel Mekki <38552193+ybelMekk@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:52:45 +0200 Subject: [PATCH 29/29] fix(vulnerability): revert ingress class tests --- internal/issue/checker/workload_v13s_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/issue/checker/workload_v13s_test.go b/internal/issue/checker/workload_v13s_test.go index 23055a450..898e467bd 100644 --- a/internal/issue/checker/workload_v13s_test.go +++ b/internal/issue/checker/workload_v13s_test.go @@ -30,12 +30,8 @@ func TestVulnerabilities_ExternalIngressActNowIssue(t *testing.T) { expectedIngress string wantIssue bool }{ - {name: "legacy external ingress class", workloadName: "ext-app-legacy", expectedIngress: "https://legacy.external.example.com", wantIssue: true}, - {name: "external haproxy ingress class", workloadName: "ext-app-haproxy", expectedIngress: "https://haproxy.external.example.com", wantIssue: true}, - {name: "external authenticated haproxy ingress class", workloadName: "ext-app-fa-haproxy", expectedIngress: "https://haproxy.fa.external.example.com", wantIssue: true}, - {name: "internal haproxy ingress class", workloadName: "internal-only-haproxy", wantIssue: false}, - {name: "unknown ingress class", workloadName: "unknown-class-ingress", wantIssue: false}, - {name: "missing ingress class", workloadName: "no-class-ingress", wantIssue: false}, + {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 {